/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <gdbus.h>

#include <connman/assert.h>
#include <connman/blob.h>
#include <connman/wifi.h>

#include "connman.h"

#define	_DBG_SERVICE(fmt, arg...)	DBG(DBG_SERVICE, fmt, ## arg)

#define MAX_CONNECT_RETRIES	5
#define CONNMAN_SERVICE_NOTIFY_SLOW 1500 /* ms */
#define CONNMAN_SERVICE_NOTIFY_FAST 125  /* ms */
#define CONNECT_TIMEOUT_SECS (45)
#define PORTAL_RECHECK_SECS 30

/*
 * Service priority is used to order services.  Priority is
 * optionally specified by clients in the range [1..100]. 
 * By default a service has no assigned priority.  Services
 * with a priority always sort before services w/o a priority.
 */
#define	CONNMAN_SERVICE_PRI_MIN		1
#define	CONNMAN_SERVICE_PRI_MAX		100
#define	CONNMAN_SERVICE_PRI_NONE	0

static DBusConnection *connection = NULL;

static GSequence *service_list = NULL;
static GHashTable *service_hash = NULL;

enum connman_service_check_portal {
	CONNMAN_SERVICE_CHECK_PORTAL_FALSE = 0,
	CONNMAN_SERVICE_CHECK_PORTAL_TRUE  = 1,
	CONNMAN_SERVICE_CHECK_PORTAL_AUTO  = 2,
};

struct online_portal_data {
	char *url;			/* URL for online portal */
	char *method;			/* HTTP method to access portal */
	char *postdata;			/* encoded data if method is POST */
};

/*
 * ID of timeout event for rechecking the portal
 */
static guint portal_recheck_timeout = 0;

struct connman_service {
	gint refcount;
	char *identifier;
	char *path;
	char *guid;
	char *uidata;
	struct connman_profile *profile;
	enum connman_service_type type;
	enum connman_service_mode mode;
	enum connman_service_security security;
	enum connman_service_state state;
	enum connman_service_error error;
	enum connman_service_check_portal check_portal;
	connman_uint8_t pri;
	connman_uint8_t strength;
	connman_bool_t favorite;
	connman_bool_t previously_connected;
	connman_bool_t hidden;
	connman_bool_t ignore;
	connman_bool_t autoconnect;
	connman_bool_t userconnect;
	connman_bool_t active;
	connman_bool_t save_credentials;
	connman_bool_t save_to_profile;
	guint writeback_task;
	GTimeVal modified;
	GTimeVal last_attempt;		/* last connection attempt */
	char *name;
	char *passphrase;

	struct {
		char *identity;
		char *eap;
		char *inner_eap;
		char *anonymous_identity;
		char *client_cert; /* formerly certpath */
		char *cert_id;
		char *private_key;
		char *private_key_passwd;
		char *key_id;
		char *ca_cert; /* formerly authpath */
		char *ca_cert_id;
		char *ca_cert_nss;
		connman_bool_t use_system_cas;
		char *pin;
		char *password;
		char *key_mgmt;
	} eap;

	char *proxy_config;

	/* TODO(sleffler) overlay storage */
	struct {
		struct online_portal_data olp;
		char *usage_url;
		enum connman_network_activation_state activation_state;
		enum connman_network_cellular_technology network_technology;
		enum connman_network_cellular_roaming_state roaming_state;
		struct connman_network_apn apn;
		struct connman_network_apn last_good_apn;
		struct connman_network_operator serving_operator;
	} cellular;
	struct {
		unsigned int wep_key_len;
		connman_uint8_t wep_key_idx;
		connman_uint8_t wep_key_matter[MAX_WEP_KEYSPACE];
		connman_bool_t hidden_ssid;
		connman_bool_t cached_hex_ssid;
		char *hex_ssid;
	} wifi;

	struct connman_device *device;
	struct connman_network *network;
	struct connman_provider *provider;
	struct sticky_route *sticky_route;
	guint timeout;
	int scangen;
	GTimeVal last_state[CONNMAN_SERVICE_STATE_MAX];

	/* we have indicated externally that this service is in the watchlist */
	connman_bool_t in_watchlist;
};

struct sticky_route {
	char *dest;
	uint32_t index;
	in_addr_t ip_addr;
	in_addr_t gw_addr;
	struct connman_rtnl rtnl;
	struct connman_service *service;
};

/*
 * This structure holds global state about "watchlist" services.  It
 * holds the g_timeout handle, and the state information we use (in
 * addition to the "in_watchlist" struct connman_service member
 * above) to iterate over services to determine whether the watchlist
 * has changed since we last sent out a notification.
 */
struct service_watchlist_state {
	guint notify_timeout;	/* timeout handle for watchlist notification */
	int count;		/* number of watchlist services globally */
	int last_count;
	connman_bool_t do_notify; /* whether we should notify on this round */
};

static struct service_watchlist_state svc_watchlist;

static int is_suspended = FALSE;
static int is_shutting_down = FALSE;

/*
 * Devices waiting for autoconnection. We hold a ref while they're on
 * here.
 */
static GQueue *auto_connect_queue = NULL;

/*
 * Service priorities: this defines an ordering for prioritizing
 * services to clients and in choosing the service through which
 * we route traffic (the default route).  Lower values are better.
 * If services are assigned the same priority we fall back to
 * other attributes (e.g. signal strength) to prioritize.
 */
static int service_priority[CONNMAN_SERVICE_TYPE_MAX];

/* TODO(sleffler): don't agree BT > WiFi etc. */
#define	CONNMAN_SERVICE_PRIO_DEFAULT	"vpn,ethernet,bluetooth,wifi,wimax,cellular"

/*
 * Security priorities: this defines an ordering for prioritizing
 * services to clients and in choosing the service through which
 * we route traffic (the default route).  Lower values are better.
 * If securities are assigned the same priority we fall back to
 * other attributes (e.g. signal strength) to prioritize.
 */
static int security_priority[CONNMAN_SERVICE_SECURITY_MAX] = {
	[CONNMAN_SERVICE_SECURITY_UNKNOWN]	= 3,
	[CONNMAN_SERVICE_SECURITY_NONE]		= 3,
	[CONNMAN_SERVICE_SECURITY_WEP]		= 2,
	[CONNMAN_SERVICE_SECURITY_WPA]		= 1,
	[CONNMAN_SERVICE_SECURITY_RSN]		= 1,
	[CONNMAN_SERVICE_SECURITY_802_1X]	= 0,
	[CONNMAN_SERVICE_SECURITY_PSK]		= 1,
};

static const char *__statename(enum connman_service_state state)
{
    static const char *statenames[] = {
        [CONNMAN_SERVICE_STATE_UNKNOWN]     = "UNKNOWN",
        [CONNMAN_SERVICE_STATE_IDLE]        = "IDLE",
        [CONNMAN_SERVICE_STATE_CARRIER]     = "CARRIER",
        [CONNMAN_SERVICE_STATE_ASSOCIATION] = "ASSOCIATION",
        [CONNMAN_SERVICE_STATE_CONFIGURATION] = "CONFIGURATION",
        [CONNMAN_SERVICE_STATE_READY]       = "READY",
        [CONNMAN_SERVICE_STATE_PORTAL]      = "PORTAL",
        [CONNMAN_SERVICE_STATE_ONLINE]      = "ONLINE",
        [CONNMAN_SERVICE_STATE_DISCONNECT]  = "DISCONNECT",
        [CONNMAN_SERVICE_STATE_FAILURE]     = "FAILURE",
        [CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE] = "ACTIVATION-FAILURE",
    };
    return statenames[state];
}

static const char* kEAPIdentity = "EAP.Identity";
static const char* kEAPEAP = "EAP.EAP";
static const char* kEAPInnerEAP = "EAP.InnerEAP";
static const char* kEAPAnonymousIdentity = "EAP.AnonymousIdentity";
static const char* kEAPClientCert = "EAP.ClientCert";
static const char* kEAPCertID = "EAP.CertID";
static const char* kEAPPrivateKey = "EAP.PrivateKey";
static const char* kEAPPrivateKeyPassword = "EAP.PrivateKeyPassword";
static const char* kEAPKeyID = "EAP.KeyID";
static const char* kEAPCACert = "EAP.CACert";
static const char* kEAPCACertID = "EAP.CACertID";
static const char* kEAPCACertNSS = "EAP.CACertNSS";
static const char* kEAPUseSystemCAs = "EAP.UseSystemCAs";
static const char* kEAPPIN = "EAP.PIN";
static const char* kEAPPassword = "EAP.Password";
static const char* kEAPKeyMgmt = "EAP.KeyMgmt";

static void profile_changed(struct connman_service *service);
static void service_writeback_cancel(struct connman_service *service);
static void service_modified(struct connman_service *service);
static void __service_resort(struct connman_service *service);
static connman_bool_t is_connected(const struct connman_service *service);
static DBusMessage *__service_set_property(struct connman_service *service,
    DBusMessageIter *entry, DBusMessage *msg);
static struct connman_service *__connman_service_get(const char *identifier,
    connman_bool_t *created_out);
static enum connman_service_mode convert_wifi_mode(const char *mode);
static enum connman_service_mode convert_wifi_security(const char *security);

static connman_bool_t is_timeset(const GTimeVal *tv)
{
	return !(tv->tv_sec == 0 && tv->tv_usec == 0);
}

static void append_path(gpointer value, gpointer user_data)
{
	struct connman_service *service = value;
	DBusMessageIter *iter = user_data;

	if (service->path == NULL || service->hidden == TRUE)
		return;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
							&service->path);
}

void __connman_service_list(DBusMessageIter *iter, void *arg)
{
	_DBG_SERVICE("");

	if (service_list != NULL)
		g_sequence_foreach(service_list, append_path, iter);
}

static inline connman_bool_t is_watchlist_state(
	enum connman_service_state state)
{
	return (state > CONNMAN_SERVICE_STATE_IDLE);
}

/*
 * Iterate over the service list and see:
 *   (a) If there are any services whose current state differs from the last
 *       state we advertised.
 *   (b) Keep a count of the total number of watchlist services we see so we
 *       can compare it to our last count.
 */
static void assess_watchlist_services(gpointer value, gpointer user_data)
{
	struct connman_service *service = value;
	struct service_watchlist_state *state = user_data;

	if (service->path == NULL || service->hidden == TRUE)
		return;

	if (is_watchlist_state(service->state)) {
		state->count++;
		if (service->in_watchlist == FALSE) {
			state->do_notify = TRUE;
			service->in_watchlist = TRUE;
		}
	} else {
		if (service->in_watchlist == TRUE) {
			state->do_notify = TRUE;
			service->in_watchlist = FALSE;
		}
	}
}

static void append_watchlist_path(gpointer value, gpointer user_data)
{
	struct connman_service *service = value;
	DBusMessageIter *iter = user_data;

	if (service->path == NULL || service->hidden == TRUE ||
	    is_watchlist_state(service->state) == FALSE)
		return;

	dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
				       &service->path);
}

void __connman_service_watchlist(DBusMessageIter *iter, void *arg)
{
	_DBG_SERVICE("");

	g_sequence_foreach(service_list, append_watchlist_path, iter);
}

static gboolean notify_watchlist(gpointer user_data)
{
	svc_watchlist.notify_timeout = 0;
	svc_watchlist.do_notify = FALSE;
	svc_watchlist.count = 0;

	g_sequence_foreach(service_list, assess_watchlist_services,
			   &svc_watchlist);

	if (svc_watchlist.last_count != svc_watchlist.count ||
	    svc_watchlist.do_notify == TRUE) {
		_DBG_SERVICE("Sending update because svc_count change is "
			     "%d -> %d and do_notify is %d",
			     svc_watchlist.last_count, svc_watchlist.count,
			     svc_watchlist.do_notify);

		svc_watchlist.last_count = svc_watchlist.count;

		connman_dbus_send_property_changed_array(
			CONNMAN_MANAGER_PATH, CONNMAN_MANAGER_INTERFACE,
			"ServiceWatchList", DBUS_TYPE_OBJECT_PATH,
			__connman_service_watchlist, NULL);

	}

	return FALSE;
}

/*
 * Outgoing watchlist notifications are batched up by actually calling
 * the notification in a timeout function.  
 */
void connman_service_notify_watchlist(enum connman_service_state state)
{
	_DBG_SERVICE("");

	if (is_watchlist_state(state)) {
		/*
		 * Notification of a service leaving the idle state
		 * is sent quickly, overriding any previous timeout
		 * (but necessarily pushing out any pending
		 * notification further into the future).
		 */
		if (svc_watchlist.notify_timeout > 0)
			g_source_remove(svc_watchlist.notify_timeout);

		svc_watchlist.notify_timeout =
			g_timeout_add(CONNMAN_SERVICE_NOTIFY_FAST,
				      notify_watchlist, NULL);
		_DBG_SERVICE("scheduled fast watchlist service notify");
	} else {
		/*
		 * Notification of services entering an idle state are
		 * pushed out with a slow timeout, but any existing timer
		 * is left intact -- so it doesn't delay any more urgent
		 * prior notification.
		 */
		if (svc_watchlist.notify_timeout == 0) {
			svc_watchlist.notify_timeout =
				g_timeout_add(CONNMAN_SERVICE_NOTIFY_SLOW,
					      notify_watchlist, NULL);
			_DBG_SERVICE("scheduled slow watchlist service notify");
		}
	}
}

struct find_data {
	const char *path;
	struct connman_service *service;
};

static void compare_path(gpointer value, gpointer user_data)
{
	struct connman_service *service = value;
	struct find_data *data = user_data;

	if (data->service != NULL)
		return;

	if (g_strcmp0(service->path, data->path) == 0)
		data->service = service;
}

static struct connman_service *find_service(const char *path)
{
	struct find_data data = { .path = path, .service = NULL };

	_DBG_SERVICE("path %s", path);

	g_sequence_foreach(service_list, compare_path, &data);

	return data.service;
}

struct connman_profile *__connman_service_get_profile(
    struct connman_service *service)
{
	return service->profile;
}

/*
 * Set the service's bound profile. 
 */
void __connman_service_set_profile(struct connman_service *service,
    struct connman_profile *profile)
{
	_DBG_SERVICE("profile %p -> %p", service->profile, profile);

	service->profile = profile;
        if (profile == NULL)
		service_writeback_cancel(service);
}

/*
 * Handle a profile being pop'd off the stack (e.g. on logout).
 * Disconnect and invalidate all services bound to the specified
 * profile.  Note that services are not always reclaimed (doing
 * so is hard given the way refcount'ing works) so we inhibit
 * saving to the profile while we scrub the in-memory state.
 */
static void invalidate_profile(gpointer value, gpointer user_data)
{
	struct connman_service *service = value;
	const struct connman_profile *profile = user_data;

	if (service->profile == profile) {
		connman_bool_t old_save;

		_DBG_SERVICE("service %s refcount %d",
		    service->identifier, service->refcount);

		service->profile = NULL;

		if (service->type == CONNMAN_SERVICE_TYPE_ETHERNET) {
			/* TODO(sleffler) really anything created by device */
			connman_error("%s: skip ethernet device", __func__);
			return;
		}

		connman_service_ref(service);
		old_save = service->save_to_profile;
		service->save_to_profile = FALSE;	/* inhibit saving */

		__connman_service_disconnect(service);
		__connman_service_reset_in_memory(service);
		if (service->provider != NULL) {
			/*
			 * Drop the reference created by Manager.GetService
			 * so the service goes away below on last unref.
			 */
			connman_service_unref(service);
		}

		service->save_to_profile = old_save;	/* restore saving */
		__connman_profile_load_service(service);

		connman_service_unref(service);
	}
}
static void service_profile_pop(struct connman_profile *profile)
{
	_DBG_SERVICE("profile %p", profile);

	g_sequence_foreach(service_list, invalidate_profile, (gpointer)profile);
	/* NB: no need to kick auto-connect; it will happen due to invalidate */
}

/*
 * Handle a profile being push'd on the stack (e.g. on login).
 * Bind/rebind service objects and kick the autoconnect logic
 * in case we can now establish connectivity.
 */
static void load_profile(gpointer value, gpointer user_data)
{
	struct connman_service *service = value;
	struct connman_profile *old_profile;

	old_profile = service->profile;
	service->profile = NULL;

	if (__connman_profile_load_service(service) != 0) {
		/* NB: no need to re-load, contents should be intact */
		service->profile = old_profile;
	} else if (old_profile != NULL) {
		/* the profile bound to the service changed */
		profile_changed(service);
	}
}
static void service_profile_push(struct connman_profile *profile)
{
	_DBG_SERVICE("profile %p", profile);

	g_sequence_foreach(service_list, load_profile, NULL);

	__connman_service_auto_connect_any();
}

static const char *identifier(struct connman_service *service)
{
	return service ? service->identifier : "<nil>";
}

static const char *service_type2string(enum connman_service_type type)
{
	switch (type) {
	case CONNMAN_SERVICE_TYPE_UNKNOWN:
		break;
	case CONNMAN_SERVICE_TYPE_ETHERNET:
		return "ethernet";
	case CONNMAN_SERVICE_TYPE_WIFI:
		return "wifi";
	case CONNMAN_SERVICE_TYPE_WIMAX:
		return "wimax";
	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
		return "bluetooth";
	case CONNMAN_SERVICE_TYPE_CELLULAR:
		return "cellular";
	case CONNMAN_SERVICE_TYPE_VPN:
		return "vpn";
	default:
		break;
	}

	return NULL;
}

enum connman_service_type __connman_service_string2type(const char *str)
{
	enum connman_service_type type = CONNMAN_SERVICE_TYPE_UNKNOWN;

	if (g_strcmp0(str, "ethernet") == 0)
		type = CONNMAN_SERVICE_TYPE_ETHERNET;
	else if (g_strcmp0(str, "wifi") == 0)
		type = CONNMAN_SERVICE_TYPE_WIFI;
	else if (g_strcmp0(str, "wimax") == 0)
		type = CONNMAN_SERVICE_TYPE_WIMAX;
	else if (g_strcmp0(str, "bluetooth") == 0)
		type = CONNMAN_SERVICE_TYPE_BLUETOOTH;
	else if (g_strcmp0(str, "cellular") == 0)
		type = CONNMAN_SERVICE_TYPE_CELLULAR;
	else if (g_strcmp0(str, "vpn") == 0)
		type = CONNMAN_SERVICE_TYPE_VPN;

	return type;
}

uint32_t __connman_service_list_to_mask(const char *list)
{
	gchar **taglist = g_strsplit(list, ",", 0);
	gchar **tagp, *tag;
	uint32_t mask;

	mask = 0;
	for (tagp = taglist; (tag = *tagp) != NULL; tagp++)
		mask |= 1 << __connman_service_string2type(tag);
	return mask;
}

gchar *__connman_service_mask_to_list(uint32_t mask)
{
	if (mask != 0) {
		gchar *tagvec[CONNMAN_SERVICE_TYPE_MAX+1];
		int i, n;

		n = 0;
		for (i = 0; i < CONNMAN_SERVICE_TYPE_MAX; i++)
			if (mask & (1<<i)) {
				const char *str = service_type2string(i);
				if (str != NULL)
					tagvec[n++] = (gchar *) str;
			}
		tagvec[n] = NULL;
		return g_strjoinv(",", tagvec);
	} else
		return g_strdup("");
}

static const char *mode2string(enum connman_service_mode mode)
{
	switch (mode) {
	case CONNMAN_SERVICE_MODE_UNKNOWN:
		break;
	case CONNMAN_SERVICE_MODE_MANAGED:
		return "managed";
	case CONNMAN_SERVICE_MODE_ADHOC:
		return "adhoc";
	}

	return NULL;
}

static const char *security2string(enum connman_service_security security)
{
	switch (security) {
	case CONNMAN_SERVICE_SECURITY_UNKNOWN:
		break;
	case CONNMAN_SERVICE_SECURITY_NONE:
		return "none";
	case CONNMAN_SERVICE_SECURITY_WEP:
		return "wep";
	case CONNMAN_SERVICE_SECURITY_WPA:
		return "wpa";
	case CONNMAN_SERVICE_SECURITY_RSN:
		return "rsn";
	case CONNMAN_SERVICE_SECURITY_802_1X:
		return "802_1x";
	case CONNMAN_SERVICE_SECURITY_PSK:
		return "psk";
	default:
		break;
	}

	return NULL;
}

const char *connman_service_get_state(const struct connman_service *service)
{
	switch (service->state) {
	case CONNMAN_SERVICE_STATE_UNKNOWN:
		break;
	case CONNMAN_SERVICE_STATE_IDLE:
		return "idle";
	case CONNMAN_SERVICE_STATE_CARRIER:
		return "carrier";
	case CONNMAN_SERVICE_STATE_ASSOCIATION:
		return "association";
	case CONNMAN_SERVICE_STATE_CONFIGURATION:
		return "configuration";
	case CONNMAN_SERVICE_STATE_READY:
		return "ready";
	case CONNMAN_SERVICE_STATE_PORTAL:
		return "portal";
	case CONNMAN_SERVICE_STATE_ONLINE:
		return "online";
	case CONNMAN_SERVICE_STATE_DISCONNECT:
		return "disconnect";
	case CONNMAN_SERVICE_STATE_FAILURE:
		return "failure";
	case CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE:
		return "activation-failure";
	default:
		break;
	}

	return NULL;
}

static const char *error2string(enum connman_service_error error)
{
	switch (error) {
	case CONNMAN_SERVICE_ERROR_NO_ERROR:
		break;
	case CONNMAN_SERVICE_ERROR_OUT_OF_RANGE:
		return "out-of-range";
	case CONNMAN_SERVICE_ERROR_PIN_MISSING:
		return "pin-missing";
	case CONNMAN_SERVICE_ERROR_DHCP_FAILED:
		return "dhcp-failed";
	case CONNMAN_SERVICE_ERROR_CONNECT_FAILED:
		return "connect-failed";
	case CONNMAN_SERVICE_ERROR_BAD_PASSPHRASE:
		return "bad-passphrase";
	case CONNMAN_SERVICE_ERROR_BAD_WEPKEY:
		return "bad-wepkey";
	case CONNMAN_SERVICE_ERROR_ACTIVATION_FAILED:
		return "activation-failed";
	case CONNMAN_SERVICE_ERROR_NEED_EVDO:
		return "need-evdo";
	case CONNMAN_SERVICE_ERROR_NEED_HOME_NETWORK:
		return "need-home-network";
	case CONNMAN_SERVICE_ERROR_OTASP_FAILED:
		return "otasp-failed";
	case CONNMAN_SERVICE_ERROR_AAA_FAILED:
		return "aaa-failed";
	case CONNMAN_SERVICE_ERROR_INTERNAL:
		return "internal-error";
	case CONNMAN_SERVICE_ERROR_DNS_LOOKUP_FAILED:
		return "dns-lookup-failed";
	case CONNMAN_SERVICE_ERROR_HTTP_GET_FAILED:
		return "http-get-failed";
	case CONNMAN_SERVICE_ERROR_IPSEC_PSK_AUTH_FAILED:
		return "ipsec-psk-auth-failed";
	case CONNMAN_SERVICE_ERROR_IPSEC_CERT_AUTH_FAILED:
		return "ipsec-cert-auth-failed";
	case CONNMAN_SERVICE_ERROR_PPP_AUTH_FAILED:
		return "ppp-auth-failed";

	default:
		break;
	}

	return NULL;
}

static enum connman_service_error string2error(const char *error)
{
	if (g_strcmp0(error, "out-of-range") == 0)
		return CONNMAN_SERVICE_ERROR_OUT_OF_RANGE;
	else if (g_strcmp0(error, "pin-missing") == 0)
		return CONNMAN_SERVICE_ERROR_PIN_MISSING;
	else if (g_strcmp0(error, "dhcp-failed") == 0)
		return CONNMAN_SERVICE_ERROR_DHCP_FAILED;
	else if (g_strcmp0(error, "connect-failed") == 0)
		return CONNMAN_SERVICE_ERROR_CONNECT_FAILED;
	else if (g_strcmp0(error, "bad-passphrase") == 0)
		return CONNMAN_SERVICE_ERROR_BAD_PASSPHRASE;
	else if (g_strcmp0(error, "bad-wepkey") == 0)
		return CONNMAN_SERVICE_ERROR_BAD_WEPKEY;
	else if (g_strcmp0(error, "activation-failed") == 0)
		return CONNMAN_SERVICE_ERROR_ACTIVATION_FAILED;
	else if (g_strcmp0(error, "need-evdo") == 0)
		 return CONNMAN_SERVICE_ERROR_NEED_EVDO;
	else if (g_strcmp0(error, "need-home-network") == 0)
		return CONNMAN_SERVICE_ERROR_NEED_HOME_NETWORK;
	else if (g_strcmp0(error, "otasp-failed") == 0)
		return CONNMAN_SERVICE_ERROR_OTASP_FAILED;
	else if (g_strcmp0(error, "aaa-failed") == 0)
		return CONNMAN_SERVICE_ERROR_AAA_FAILED;
	else if (g_strcmp0(error, "internal-error") == 0)
		return CONNMAN_SERVICE_ERROR_INTERNAL;

	return CONNMAN_SERVICE_ERROR_NO_ERROR;
}

static const char *activation_state2string(
	enum connman_network_activation_state state)
{
	switch (state) {
	case CONNMAN_NETWORK_ACTIVATION_STATE_UNKNOWN:
		return "unknown";
	case CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATED:
		return "activated";
	case CONNMAN_NETWORK_ACTIVATION_STATE_ACTIVATING:
		return "activating";
	case CONNMAN_NETWORK_ACTIVATION_STATE_NOT_ACTIVATED:
		return "not-activated";
	case CONNMAN_NETWORK_ACTIVATION_STATE_PARTIALLY_ACTIVATED:
		return "partially-activated";
	}
	return NULL;
}

static const char *network_technology2string(
	enum connman_network_cellular_technology technology)
{
	switch (technology) {
	case CONNMAN_NETWORK_TECHNOLOGY_1XRTT:
		return "1xRTT";
	case CONNMAN_NETWORK_TECHNOLOGY_EVDO:
		return "EVDO";
	case CONNMAN_NETWORK_TECHNOLOGY_GSM:
		return "GSM";
	case CONNMAN_NETWORK_TECHNOLOGY_GPRS:
		return "GPRS";
	case CONNMAN_NETWORK_TECHNOLOGY_EDGE:
		return "EDGE";
	case CONNMAN_NETWORK_TECHNOLOGY_UMTS:
		return "UMTS";
	case CONNMAN_NETWORK_TECHNOLOGY_HSPA:
		return "HSPA";
	case CONNMAN_NETWORK_TECHNOLOGY_HSPA_PLUS:
		return "HSPA+";
	case CONNMAN_NETWORK_TECHNOLOGY_LTE:
		return "LTE";
	case CONNMAN_NETWORK_TECHNOLOGY_LTE_ADVANCED:
		return "LTE Advanced";
	default:
		break;
	}
	return NULL;
}

static const char *roaming_state2string(
	enum connman_network_cellular_roaming_state state)
{
	switch (state) {
	case CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN:
		return "unknown";
	case CONNMAN_NETWORK_ROAMING_STATE_HOME:
		return "home";
	case CONNMAN_NETWORK_ROAMING_STATE_ROAMING:
		return "roaming";
	}
	return NULL;
}

static void property_changed_boolean(struct connman_service *service,
			     const char *prop_name, connman_bool_t value)
{
	if (service->path == NULL)
		return;
	connman_dbus_send_property_changed_variant(service->path,
	    CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_BOOLEAN, &value);
}

static void property_changed_string(struct connman_service *service,
			     const char *prop_name, const char *value)
{
	if (service->path == NULL || value == NULL)
		return;
	connman_dbus_send_property_changed_variant(service->path,
	    CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_STRING, &value);
}

static void property_changed_int32(struct connman_service *service,
			     const char *prop_name, int32_t value)
{
	if (service->path == NULL)
		return;
	connman_dbus_send_property_changed_variant(service->path,
	    CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_INT32, &value);
}

static void property_changed_byte(struct connman_service *service,
			     const char *prop_name, uint8_t value)
{
	if (service->path == NULL)
		return;
	connman_dbus_send_property_changed_variant(service->path,
	    CONNMAN_SERVICE_INTERFACE, prop_name, DBUS_TYPE_BYTE, &value);
}

static struct connman_service *get_active_service(void)
{
	struct connman_service *service;
	GSequenceIter *iter;

	iter = g_sequence_get_begin_iter(service_list);

	if (g_sequence_iter_is_end(iter) == TRUE) {
		_DBG_SERVICE("no services");
		return NULL;
	}
	service = g_sequence_get(iter);
	if (service == NULL) {
		_DBG_SERVICE("NULL service");
		return NULL;
	}
	if (!is_connected(service)) {
		_DBG_SERVICE("service %p not connected (%s)", service,
		    connman_service_get_state(service));
		return NULL;
	}
	return service;
}

const char *__connman_service_default(void)
{
	return connman_service_get_type(get_active_service());
}

static void notify_state_changed(struct connman_service *service)
{
	const char *str;

	str = connman_service_get_state(service);
	if (str == NULL)
		return;

	switch (service->state) {
	case CONNMAN_SERVICE_STATE_PORTAL:
	case CONNMAN_SERVICE_STATE_FAILURE:
	case CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE:
		property_changed_string(service, "Error",
		    error2string(service->error));
		break;
	default:
		break;
	}

	property_changed_string(service, "State", str);
}

static void strength_changed(struct connman_service *service)
{
	if (service->strength != 0)
		property_changed_byte(service, "Strength", service->strength);
}

/*
 * Return TRUE if a passphrase is required to use the service.
 */
static connman_bool_t is_passphrase_required(
	const struct connman_service *service)
{
	switch (service->security) {
	case CONNMAN_SERVICE_SECURITY_WEP:
	case CONNMAN_SERVICE_SECURITY_WPA:
	case CONNMAN_SERVICE_SECURITY_RSN:
	case CONNMAN_SERVICE_SECURITY_PSK:
		if (service->passphrase == NULL)
			return TRUE;		/* must have a passphrase */
		if (service->state == CONNMAN_SERVICE_STATE_FAILURE &&
		    service->error == CONNMAN_SERVICE_ERROR_BAD_PASSPHRASE)
			return TRUE;		/* bad passphrase */
		if (service->favorite == TRUE)
			return FALSE;		/* successfully used before */
		/* if passphrase was never used, assume it is correct */
		return is_timeset(&service->last_attempt);

	case CONNMAN_SERVICE_SECURITY_802_1X:
		/*
		 * Passphrases are not mandatory for 802.1x - private
		 * key files may not be encrypted, and authentication
		 * via PKCS#11 doesn't use passphrases.
		 *
		 * TODO(sleffler) true 'cuz we only support EAP-TLS
		 */
		return FALSE;
	default:
		break;
	}
	return FALSE;
}

static connman_bool_t is_key_mgmt_1x(const char *key_mgmt)
{
	return (key_mgmt != NULL &&
	    /* TODO(sleffler) drop case requirement when plugin handles it */
	    g_strcmp0(key_mgmt, CONNMAN_WIFI_EAP_KEY_MGMT_1X) == 0);
}

static connman_bool_t is_connectable(const struct connman_service *service)
{

	if (service->security == CONNMAN_SERVICE_SECURITY_802_1X ||
	    (service->security == CONNMAN_SERVICE_SECURITY_WEP &&
	     is_key_mgmt_1x(service->eap.key_mgmt) == TRUE)) {
		/* Identity is required for EAP. */
		if (service->eap.identity == NULL) {
			_DBG_SERVICE("NOT CONNECTABLE: identity is NULL");
			return FALSE;
		}

		/*
		 * If a client certificate is being used, a private
		 * key is required.
		 */
		if ((service->eap.client_cert != NULL ||
		     service->eap.cert_id != NULL) &&
		    (service->eap.private_key == NULL &&
		     service->eap.key_id == NULL)) {
			_DBG_SERVICE("NOT CONNECTABLE: client cert but no "
			    "privkey");
			return FALSE;
		}

		/* If any PKCS#11 data is needed, a PIN is required. */
		if ((service->eap.cert_id != NULL ||
		     service->eap.key_id != NULL ||
		     service->eap.ca_cert_id != NULL) &&
		    service->eap.pin == NULL) {
			_DBG_SERVICE("NOT CONNECTABLE: pkcs#11 but no PIN");
			return FALSE;
		}

		/*
		 * There may be more than one EAP type; consider the
		 * service connectable if at least one combination
		 * makes sense.
		 */
		if (service->eap.eap == NULL ||
		    strstr(service->eap.eap, "TLS") != NULL) {
			/* EAP-TLS requires a client certificate. */
			if (service->eap.client_cert != NULL ||
			    service->eap.cert_id != NULL) {
				_DBG_SERVICE("CONNECTABLE: EAP-TLS and has a "
				    "client cert");
				return TRUE;
			}
		}
		if (service->eap.eap == NULL ||
		    strcmp(service->eap.eap, "TLS") != 0) {
			/*
			 * For EAP types other than EAP-TLS,
			 * identity+password is the minimum
			 * requirement; the password is used either
			 * for the outer auth (EAP-MD5) or the inner
			 * auth (PEAP/MSCHAPv2, EAP-TLS/CHAP,
			 * etc.... nobody sane tunnels EAP-TLS inside
			 * of EAP-TTLS, and we don't have all the
			 * client_cert2, etc. properties needed to
			 * support that anyway).
			 */
			if (service->eap.password != NULL) {
				_DBG_SERVICE("CONNECTABLE: non-EAP-TLS and "
				    "has a password");
				return TRUE;
			}
		}
		_DBG_SERVICE("NOT CONNECTABLE: no suitable EAP configuration "
		    "set");
		return FALSE;
	}
	/* TODO(njw): Check WEP keys or WPA passphrases here? */

	return TRUE;
}

static void profile_changed(struct connman_service *service)
{
	CONNMAN_ASSERT(service->profile != NULL);

	property_changed_string(service, "Profile",
	    __connman_profile_get_path(service->profile));
}

static void get_properties_802_1x(DBusMessageIter *dict,
    struct connman_service *service, gboolean has_privs)
{
	if (service->eap.identity != NULL)
		connman_dbus_dict_append_variant(dict, kEAPIdentity,
			DBUS_TYPE_STRING, &service->eap.identity);
	if (service->eap.eap != NULL)
		connman_dbus_dict_append_variant(dict, kEAPEAP,
			DBUS_TYPE_STRING, &service->eap.eap);
	if (service->eap.inner_eap != NULL)
		connman_dbus_dict_append_variant(dict, kEAPInnerEAP,
			DBUS_TYPE_STRING, &service->eap.inner_eap);
	if (service->eap.anonymous_identity != NULL)
		connman_dbus_dict_append_variant(dict, kEAPAnonymousIdentity,
                        DBUS_TYPE_STRING, &service->eap.anonymous_identity);
	if (service->eap.client_cert != NULL)
		connman_dbus_dict_append_variant(dict, kEAPClientCert,
                        DBUS_TYPE_STRING, &service->eap.client_cert);
	if (service->eap.cert_id != NULL)
		connman_dbus_dict_append_variant(dict, kEAPCertID,
			DBUS_TYPE_STRING, &service->eap.cert_id);
	if (service->eap.private_key != NULL)
		connman_dbus_dict_append_variant(dict, kEAPPrivateKey,
			DBUS_TYPE_STRING, &service->eap.private_key);
	if (service->eap.private_key_passwd != NULL && has_privs == TRUE)
		connman_dbus_dict_append_variant(dict, kEAPPrivateKeyPassword,
			DBUS_TYPE_STRING, &service->eap.private_key_passwd);
	if (service->eap.key_id != NULL)
		connman_dbus_dict_append_variant(dict, kEAPKeyID,
			DBUS_TYPE_STRING, &service->eap.key_id);
	if (service->eap.ca_cert != NULL)
		connman_dbus_dict_append_variant(dict, kEAPCACert,
			DBUS_TYPE_STRING, &service->eap.ca_cert);
	if (service->eap.ca_cert_id != NULL)
		connman_dbus_dict_append_variant(dict, kEAPCACertID,
			DBUS_TYPE_STRING, &service->eap.ca_cert_id);
	if (service->eap.ca_cert_nss != NULL)
		connman_dbus_dict_append_variant(dict, kEAPCACertNSS,
			DBUS_TYPE_STRING, &service->eap.ca_cert_nss);
	connman_dbus_dict_append_variant(dict, kEAPUseSystemCAs,
		DBUS_TYPE_BOOLEAN, &service->eap.use_system_cas);
	if (service->eap.pin != NULL)
		connman_dbus_dict_append_variant(dict, kEAPPIN,
			DBUS_TYPE_STRING, &service->eap.pin);
	if (service->eap.password != NULL && has_privs == TRUE)
		connman_dbus_dict_append_variant(dict, kEAPPassword,
			DBUS_TYPE_STRING, &service->eap.password);
	if (service->eap.key_mgmt != NULL)
		connman_dbus_dict_append_variant(dict, kEAPKeyMgmt,
			DBUS_TYPE_STRING, &service->eap.key_mgmt);
}

static void append_apn(DBusMessageIter *iter, void *arg)
{
	struct connman_network_apn *apn = (struct connman_network_apn *)arg;

	if (apn->apn != NULL)
		connman_dbus_dict_append_string(iter, "apn", apn->apn);
	if (apn->network_id != NULL)
		connman_dbus_dict_append_string(iter, "network_id",
						apn->network_id);
	if (apn->username != NULL)
		connman_dbus_dict_append_string(iter, "username",
					apn->username);
	if (apn->password != NULL)
		connman_dbus_dict_append_string(iter, "password",
					apn->password);
}

static void get_apn_property(DBusMessageIter *dict,
			       struct connman_network_apn *apn,
			       const char *keytag)
{
	gchar *apn_key = g_strdup_printf("Cellular.%s", keytag);

	connman_dbus_dict_append_string_dict(dict, apn_key, append_apn, apn);
	g_free(apn_key);
}

static void append_operator(DBusMessageIter *iter, void *arg)
{
	struct connman_network_operator *op =
		(struct connman_network_operator *)arg;

	if (op->name != NULL)
		connman_dbus_dict_append_string(iter, "name", op->name);
	if (op->country != NULL)
		connman_dbus_dict_append_string(iter, "country", op->country);
	if (op->code != NULL)
		connman_dbus_dict_append_string(iter, "code", op->code);
}

static void append_online_portal(DBusMessageIter *iter, void *arg)
{
	const struct online_portal_data *olp =
		(struct online_portal_data *)arg;

	if (olp->url != NULL)
		connman_dbus_dict_append_string(iter, "url", olp->url);
	if (olp->method != NULL)
		connman_dbus_dict_append_string(iter, "method", olp->method);
	if (olp->postdata != NULL)
		connman_dbus_dict_append_string(iter, "postdata", olp->postdata);
}

static const char *check_portal_to_str(enum connman_service_check_portal v)
{
	if (v == CONNMAN_SERVICE_CHECK_PORTAL_TRUE)
		return "true";
	if (v == CONNMAN_SERVICE_CHECK_PORTAL_FALSE)
		return "false";
	return "auto";
}

static enum connman_service_check_portal check_portal_from_str(const char *str)
{
	if (g_ascii_strcasecmp(str, "true") == 0)
		return CONNMAN_SERVICE_CHECK_PORTAL_TRUE;
	if (g_ascii_strcasecmp(str, "false") == 0)
		return CONNMAN_SERVICE_CHECK_PORTAL_FALSE;
	return CONNMAN_SERVICE_CHECK_PORTAL_AUTO;
}

static DBusMessage *get_properties(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	DBusMessage *reply;
	DBusMessageIter array, dict;
	dbus_bool_t required, connectable;
	const char *str;
	struct connman_device *device;

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_iter_init_append(reply, &array);

	dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	str = service_type2string(service->type);
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "Type",
						DBUS_TYPE_STRING, &str);

	str = mode2string(service->mode);
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "Mode",
						DBUS_TYPE_STRING, &str);

	str = security2string(service->security);
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "Security",
						DBUS_TYPE_STRING, &str);

	str = connman_service_get_state(service);
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "State",
						DBUS_TYPE_STRING, &str);

	if (service->profile != NULL) {
		str = __connman_profile_get_path(service->profile);
		connman_dbus_dict_append_variant(&dict, "Profile",
						DBUS_TYPE_STRING, &str);
	}

	str = error2string(service->error);
	if (str != NULL)
		connman_dbus_dict_append_variant(&dict, "Error",
						DBUS_TYPE_STRING, &str);

	if (service->pri != CONNMAN_SERVICE_PRI_NONE) {
		int pri = service->pri;
		connman_dbus_dict_append_variant(&dict, "Priority",
					DBUS_TYPE_INT32, &pri);
	}

	if (service->strength > 0)
		connman_dbus_dict_append_variant(&dict, "Strength",
					DBUS_TYPE_BYTE, &service->strength);

	connman_dbus_dict_append_variant(&dict, "Favorite",
					DBUS_TYPE_BOOLEAN, &service->favorite);

	connectable = is_connectable(service);
	connman_dbus_dict_append_variant(&dict, "Connectable",
					 DBUS_TYPE_BOOLEAN, &connectable);

	if (service->favorite == TRUE)
		connman_dbus_dict_append_variant(&dict, "AutoConnect",
				DBUS_TYPE_BOOLEAN, &service->autoconnect);
	else
		connman_dbus_dict_append_variant(&dict, "AutoConnect",
					DBUS_TYPE_BOOLEAN, &service->favorite);

	connman_dbus_dict_append_variant(&dict, "SaveCredentials",
				DBUS_TYPE_BOOLEAN, &service->save_credentials);

	str = check_portal_to_str(service->check_portal);
	connman_dbus_dict_append_variant(&dict, "CheckPortal",
					DBUS_TYPE_STRING, &str);

	connman_dbus_dict_append_variant(&dict, "IsActive",
					DBUS_TYPE_BOOLEAN, &service->active);

	if (service->name != NULL)
		connman_dbus_dict_append_variant(&dict, "Name",
					DBUS_TYPE_STRING, &service->name);

	if (service->guid != NULL)
		connman_dbus_dict_append_variant(&dict, "GUID",
					DBUS_TYPE_STRING, &service->guid);

	if (service->uidata != NULL)
		connman_dbus_dict_append_variant(&dict, "UIData",
					DBUS_TYPE_STRING, &service->uidata);

	device = connman_service_get_device(service);
	if (device != NULL) {
		const char *path = connman_device_get_path(device);
		if (path != NULL)
			connman_dbus_dict_append_variant(&dict, "Device",
					DBUS_TYPE_OBJECT_PATH, &path);
	}

	switch (service->type) {
	case CONNMAN_SERVICE_TYPE_UNKNOWN:
	case CONNMAN_SERVICE_TYPE_ETHERNET:
	case CONNMAN_SERVICE_TYPE_WIMAX:
	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
		break;
	case CONNMAN_SERVICE_TYPE_CELLULAR:
		if (service->cellular.activation_state !=
		    CONNMAN_NETWORK_ACTIVATION_STATE_UNKNOWN) {
			const char *state_str = activation_state2string(
				service->cellular.activation_state);

			connman_dbus_dict_append_variant(&dict,
				"Cellular.ActivationState", DBUS_TYPE_STRING,
				&state_str);
		}
		if (service->cellular.serving_operator.code != NULL)
			connman_dbus_dict_append_string_dict(
				&dict,
				"Cellular.ServingOperator",
				append_operator,
				&service->cellular.serving_operator);
		if (service->cellular.network_technology !=
		    CONNMAN_NETWORK_TECHNOLOGY_UNKNOWN) {
			const char *tech_str = network_technology2string(
				service->cellular.network_technology);
			connman_dbus_dict_append_variant(&dict,
				"Cellular.NetworkTechnology", DBUS_TYPE_STRING,
				&tech_str);
		}
		if (service->cellular.roaming_state !=
		    CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN) {
			const char *roaming_str = roaming_state2string(
				service->cellular.roaming_state);
			connman_dbus_dict_append_variant(&dict,
				"Cellular.RoamingState", DBUS_TYPE_STRING,
				&roaming_str);
		}
		if (service->cellular.olp.url != NULL) {
			connman_dbus_dict_append_string_dict(
				&dict,
				"Cellular.Olp",
				append_online_portal,
				&service->cellular.olp);

			/* TODO(jglasgow): remove after chrome updated */
			connman_dbus_dict_append_variant(&dict,
				"Cellular.OlpUrl", DBUS_TYPE_STRING,
				&service->cellular.olp.url);
		}
		if (service->cellular.usage_url != NULL) {
			connman_dbus_dict_append_variant(&dict,
				"Cellular.UsageUrl", DBUS_TYPE_STRING,
				&service->cellular.usage_url);
		}
		if (service->cellular.apn.apn != NULL)
			get_apn_property(&dict, &service->cellular.apn, "APN");
		if (service->cellular.last_good_apn.apn != NULL)
			get_apn_property(&dict, &service->cellular.last_good_apn,
					 "LastGoodAPN");
		break;
	case CONNMAN_SERVICE_TYPE_WIFI:
		connman_dbus_dict_append_variant(&dict, "WiFi.HiddenSSID",
			DBUS_TYPE_BOOLEAN, &service->wifi.hidden_ssid);

		if (service->network != NULL) {
			connman_uint16_t frequency;
			enum connman_network_phymode phymode;

			frequency = connman_network_get_uint16(service->network,
			    "Frequency");
			if (frequency > 0)
				connman_dbus_dict_append_variant(&dict,
				    "WiFi.Frequency", DBUS_TYPE_UINT16,
				    &frequency);

			phymode = connman_network_get_phymode(service->network);
			if (phymode != CONNMAN_NETWORK_PHYMODE_UNDEF) {
				const char *str =
				    connman_network_get_phymode_name(phymode);
				connman_dbus_dict_append_variant(&dict,
				    "WiFi.PhyMode", DBUS_TYPE_STRING, &str);
			}

			str = connman_network_get_string(service->network,
			    CONNMAN_WIFI_AUTHMODE);
			if (str != NULL)
				connman_dbus_dict_append_variant(&dict,
				    CONNMAN_WIFI_AUTHMODE, DBUS_TYPE_STRING,
				    &str);

			str = connman_network_get_string(service->network,
			    "Address");
			if (str != NULL)
				connman_dbus_dict_append_variant(&dict,
				    CONNMAN_WIFI_BSSID, DBUS_TYPE_STRING,
				    &str);

			if (!service->wifi.cached_hex_ssid) {
				unsigned int ssid_len;
				const void *ssid_blob;

				/*
				 * Read the binary form of the SSID out of
				 * the network object.  If it does not match
				 * the service name, store a hex-encoded copy
				 * of the SSID as a property of the service.
				 */
				ssid_blob = connman_network_get_blob(
					   service->network, "WiFi.SSID",
					   &ssid_len);
				if (ssid_len != strlen(service->name) ||
				    memcmp(service->name, ssid_blob,
					   ssid_len) != 0)
					service->wifi.hex_ssid =
						connman_network_get_hex_ssid(
						    service->network);
				service->wifi.cached_hex_ssid = TRUE;
			}
		}

		if (service->wifi.hex_ssid != NULL) {
			connman_dbus_dict_append_variant(&dict,
			    "WiFi.HexSSID", DBUS_TYPE_STRING,
			    &service->wifi.hex_ssid);
		}

		if (__connman_security_check_privilege(msg,
			CONNMAN_SECURITY_PRIVILEGE_READ_SECRET) == 0) {
			if (service->passphrase != NULL)
				connman_dbus_dict_append_variant(
                        		&dict, "Passphrase", DBUS_TYPE_STRING,
					&service->passphrase);
			get_properties_802_1x(&dict, service, TRUE);
		} else
			get_properties_802_1x(&dict, service, FALSE);

		required = is_passphrase_required(service);
		connman_dbus_dict_append_variant(&dict, "PassphraseRequired",
						DBUS_TYPE_BOOLEAN, &required);
		break;
	case CONNMAN_SERVICE_TYPE_VPN:
		if (service->provider != NULL) {
			struct __connman_provider_append_properties_args args;
			args.msg = msg;
			args.provider = service->provider;
			connman_dbus_dict_append_dict(&dict, "Provider",
			    __connman_provider_append_properties, &args);
		}
		break;
	default:
		break;
	}

	if (service->proxy_config != NULL)
		connman_dbus_dict_append_variant(&dict, "ProxyConfig",
						 DBUS_TYPE_STRING,
						 &service->proxy_config);
	if (service->sticky_route != NULL)
		connman_dbus_dict_append_variant(&dict, "StickyHostRoute",
						 DBUS_TYPE_STRING,
						 &service->sticky_route->dest);

	dbus_message_iter_close_container(&array, &dict);

	return reply;
}

static uint8_t hex(const char c)
{
	if ('0' <= c && c <= '9')
		return c - '0';
	else if ('a' <= c && c <= 'f')
		return 10 + c - 'a';
	else
		return 10 + c - 'A';
}

static connman_bool_t cvthexstring(uint8_t data[MAX_WEP_KEYSPACE],
    const char *string, int len)
{
	int i;

	/* NB: check string first before altering data */
	for (i = 0; i < len; i++)
		if (!isxdigit(string[i])) {
			connman_error("invalid char 0x%x in hex key",
			    string[i]);
			return FALSE;
		}
	for (i = 0; i < len; i += 2)
		data[i/2] = (hex(string[i]) << 4) | hex(string[i+1]);
	return TRUE;
}

/*
 * Validate and convert user-supplied WEP key data.
 * Key lengths are 5 or 13 octets of data.  When specified as
 * ascii hex digits that's 10 or 26 bytes (2 ascii chars = 1 octet).
 * An optional wep key index can be specified by including an
 * "N:" prefix.  An optional C-style "0x" prefix for hex key
 * matter is also supported.
 *
 * When a wep key index is specified we end up with lengths that
 * are +2 the 5/13 (or 10/26) values.  When an optional 0x
 * prefix is given before the key matter that's +2 those values.
 *
 * TODO(vlaviano): this function could benefit from a unit test; infra?
 */
static connman_bool_t __connman_service_parse_wep_key(const char *data,
    connman_uint8_t *wep_key_idx, unsigned int *wep_key_len,
    connman_uint8_t wep_key_matter[MAX_WEP_KEYSPACE])
{
	int key_idx = 0;
	int len = strlen(data);

	/* verify string is long enough for parsing checks */
	if (len < 5) {
		connman_error("wep key too short, len  %d", len);
		return FALSE;
	}
	/*
	 * Handle key index, but only if key length could be valid
	 * (taking into account a possible 0x prefix for hex keys).
	 * This allows us to accept ASCII strings that start with
	 * "0:", "1:", "2:", or "3:".
	 */
	if (data[1] == ':' &&
	    (len == 2+5 || len == 2+2*5 || len == 2+2+2*5 ||
	     len == 2+13 || len == 2+2*13 || len == 2+2+2*13)) {
		if (!('0' <= data[0] && data[0] <= '3')) {
			connman_error("invalid wep key index \"%c\"", data[0]);
			return FALSE;
		}
		key_idx = data[0] - '0';
		data += 2;
		len -= 2;
	}
	/*
	 * Ignore leading C-style 0x syntax in key material, but only
	 * if length indicates that this is a hex key rather than an
	 * ASCII strings. This allows us to handle strings that start
	 * with 0x or 0X.
	 */
	if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') &&
	    (len == 2+2*5 || len == 2+2*13)) {
		data += 2;
		len -= 2;
	}
	/*
	 * Accept 40- and 104-bit keys. Could check for weak keys but not
	 * worth the effort; this work is to simplify later data handling.
	 */
	if (len == 2*5 || len == 2*13) {
		/* hex string key matter, validate hex digits */
		if (!cvthexstring(wep_key_matter, data, len))
			return FALSE;
		*wep_key_len = len / 2;
		*wep_key_idx = key_idx;
		return TRUE;
	} else if (len == 5 || len == 13) {
		/* ascii string, no checking possible, just convert */
		memcpy(wep_key_matter, data, len);
		*wep_key_len = len;
		*wep_key_idx = key_idx;
		return TRUE;
	} else {
		connman_error("invalid wep key length %d", len);
		return FALSE;
	}
}

/*
 * Helper function for validating WPA and RSN passphrases
 */
static connman_bool_t __connman_service_validate_passphrase(const char *data)
{
	connman_uint8_t pmk_key_matter[WPA_PMK_KEYLENGTH];
	int len = strlen(data);
	int i;

	/* verify passphrase length is in the range [8-63] */
	if (len < 8) {
		connman_error("passphrase too short, len %d", len);
		return FALSE;
	} else if (len == WPA_PMK_KEYLENGTH * 2 &&
		   cvthexstring(pmk_key_matter, data, len)) {
		/* This is a raw PMK string */
		return TRUE;
	} else if (len > 63) {
		connman_error("passphrase too long, len %d", len);
		return FALSE;
	}

	for (i = 0; i < len; i++) {
		if (!g_ascii_isprint(data[i])) {
			connman_error("passphrase contains non-printable "
				      "characters");
			return FALSE;
		}
	}

	return TRUE;
}

static void service_priority_changed(struct connman_service *service)
{
	property_changed_int32(service, "Priority", service->pri);
}

/*
 * Helper functions for pushing service state down into the active network.
 */

static void prepare_network_wep(struct connman_service *service)
{
	connman_network_set_uint8(service->network, "WiFi.WEPKeyIndex",
	    service->wifi.wep_key_idx);
	connman_network_set_blob(service->network, "WiFi.WEPKey",
	    service->wifi.wep_key_matter, service->wifi.wep_key_len);
}

static void prepare_network_passphrase(struct connman_service *service)
{
	connman_network_set_string(service->network, "WiFi.Passphrase",
	    service->passphrase);
	if (service->security == CONNMAN_SERVICE_SECURITY_WEP)
		prepare_network_wep(service);
}

static void prepare_network_string(struct connman_service *service,
				   const char *name, const char *value)
{
	connman_network_set_string(service->network, name, value);
}

static void prepare_network_eap(struct connman_service *service)
{
	prepare_network_string(service, CONNMAN_WIFI_EAP_IDENTITY,
			       service->eap.identity);
	prepare_network_string(service, CONNMAN_WIFI_EAP_EAP,
			       service->eap.eap);
	prepare_network_string(service, CONNMAN_WIFI_EAP_INNEREAP,
			       service->eap.inner_eap);
	prepare_network_string(service, CONNMAN_WIFI_EAP_ANONYMOUSIDENTITY,
			       service->eap.anonymous_identity);
	prepare_network_string(service, CONNMAN_WIFI_EAP_CLIENTCERT,
			       service->eap.client_cert);
	prepare_network_string(service, CONNMAN_WIFI_EAP_CERTID,
			       service->eap.cert_id);
	prepare_network_string(service, CONNMAN_WIFI_EAP_PRIVATEKEY,
			       service->eap.private_key);
	prepare_network_string(service, CONNMAN_WIFI_EAP_PRIVATEKEYPASSWORD,
			       service->eap.private_key_passwd);
	prepare_network_string(service, CONNMAN_WIFI_EAP_KEYID,
			       service->eap.key_id);
	prepare_network_string(service, CONNMAN_WIFI_EAP_CACERT,
			       service->eap.ca_cert);
	prepare_network_string(service, CONNMAN_WIFI_EAP_CACERTID,
			       service->eap.ca_cert_id);
	prepare_network_string(service, CONNMAN_WIFI_EAP_CACERTNSS,
			       service->eap.ca_cert_nss);
	connman_network_set_uint8(service->network,
		CONNMAN_WIFI_EAP_USESYSTEMCAS,
		service->eap.use_system_cas);
	prepare_network_string(service, CONNMAN_WIFI_EAP_PIN,
			       service->eap.pin);
	prepare_network_string(service, CONNMAN_WIFI_EAP_PASSWORD,
			       service->eap.password);
	prepare_network_string(service, CONNMAN_WIFI_EAP_KEY_MGMT,
			       service->eap.key_mgmt);
}


static DBusMessage *set_eap_string(struct connman_service *service,
				   DBusMessage *msg,
				   int type,
				   DBusMessageIter *value,
				   char **storage,
				   const char *servicename,
				   const char *networkname)
{
	const char *string;

	if (type != DBUS_TYPE_STRING)
		return __connman_error_invalid_arguments(msg);

	dbus_message_iter_get_basic(value, &string);

	g_free(*storage);
	*storage = g_strdup(string);

	if (service->network != NULL)
		prepare_network_string(service, networkname,
				       *storage);

	property_changed_string(service, servicename, *storage);

	service_modified(service);

	return NULL;
}

static void clear_str(char **pstr)
{
	g_free(*pstr);
	*pstr = NULL;
}

static gboolean isvalidstr(const char *str)
{
	return (str != NULL && strlen(str) > 0);
}

static void free_apn(struct connman_service *service)
{
	struct connman_network_apn *apn = &service->cellular.apn;

	clear_str(&apn->apn);
	clear_str(&apn->username);
	clear_str(&apn->password);
	clear_str(&apn->network_id);
}

static DBusMessage *clear_apn(struct connman_service *service, DBusMessage *msg)
{
	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	free_apn(service);
	connman_dbus_send_property_changed_string_dict(service->path,
					  CONNMAN_SERVICE_INTERFACE,
					  "Cellular.APN", append_apn,
					  &service->cellular.apn);
	return NULL;
}

static DBusMessage *set_apn_from_property(struct connman_service *service,
				  DBusMessage *msg,
				  gint type,
				  DBusMessageIter *value)
{
	DBusMessageIter array;
	struct connman_network_apn *apn = &service->cellular.apn;
	const char *apn_value = NULL;
	const char *username = NULL;
	const char *password = NULL;
	const char *network_id = NULL;

	_DBG_SERVICE("service %s", service->identifier);

	if (type != DBUS_TYPE_ARRAY)
		return __connman_error_invalid_arguments(msg);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	free_apn(service);
	dbus_message_iter_recurse(value, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry;
		const char *key;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);
		dbus_message_iter_next(&entry);
		if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		if (g_str_equal(key, "apn") == TRUE)
			dbus_message_iter_get_basic(&entry, &apn_value);
		else if (g_str_equal(key, "network_id"))
			dbus_message_iter_get_basic(&entry, &network_id);
		else if (g_str_equal(key, "username"))
			dbus_message_iter_get_basic(&entry, &username);
		else if (g_str_equal(key, "password"))
			dbus_message_iter_get_basic(&entry, &password);
		else
			connman_warn("%s: unknown APN property %s",
				     __func__, key);
		dbus_message_iter_next(&array);
	}
	if (isvalidstr(apn_value)) {
		apn->apn = g_strdup(apn_value);
		if (isvalidstr(network_id))
			apn->network_id = g_strdup(network_id);
		else
			apn->network_id =
				g_strdup(service->cellular
					 .serving_operator.code);
		if (isvalidstr(username))
			apn->username = g_strdup(username);
		if (isvalidstr(password))
			apn->password = g_strdup(password);
		/*
		 * Clear the last-good-apn, otherwise the one
		 * the user just set won't be used.
		 */
		__connman_service_save_last_good_apn(service, NULL);
	}

	__connman_profile_save_service(service);
	connman_dbus_send_property_changed_string_dict(service->path,
					  CONNMAN_SERVICE_INTERFACE,
					  "Cellular.APN", append_apn, apn);
	return NULL;
}

static void sticky_newroute(void *user_data, int index, int scope,
    const char *dst, const char *gateway)
{
	struct connman_device *device;
	const char *new_gateway;
	int err;
	struct sticky_route *sr = user_data;

	_DBG_SERVICE("");

	CONNMAN_ASSERT(sr != NULL);
	CONNMAN_ASSERT(sr->service != NULL);

	if (sr->index != index)
		return;
	if (scope != RT_SCOPE_UNIVERSE || g_strcmp0(dst, "0.0.0.0") != 0)
		return;

	err = connman_inet_del_hostroute(sr->index, sr->ip_addr, sr->gw_addr);
	if (err < 0)
		connman_warn("%s: del hostroute failed: %d", __func__, err);

	device = connman_service_get_device(sr->service);
	if (device == NULL) {
		connman_error("%s: no device", __func__);
		return;
	}

	sr->index = connman_device_get_index(device);

	new_gateway = __connman_ipconfig_get_gateway(sr->index);
	if (new_gateway == NULL) {
		connman_error("%s: no gateway", __func__);
		return;
	}

	sr->gw_addr = inet_addr(new_gateway);

	err = connman_inet_add_hostroute(sr->index, sr->ip_addr, sr->gw_addr);
	if (err < 0) {
		connman_error("%s: add hostroute failed: %d", __func__, err);
		return;
	}
}

static struct sticky_route *new_sticky_route(
    struct connman_service *service, const char *ip_addr)
{
	struct sticky_route *sr;
        struct connman_device *device;
	const char *gateway;

	_DBG_SERVICE("");

	CONNMAN_ASSERT(service != NULL);
	CONNMAN_ASSERT(ip_addr != NULL);
	CONNMAN_ASSERT(service->sticky_route == NULL);

	sr = g_try_new0(struct sticky_route, 1);
	if (sr == NULL) {
		connman_error("%s: no memory", __func__);
		return NULL;
	}

	sr->ip_addr = inet_addr(ip_addr);

        device = connman_service_get_device(service);
	if (device == NULL) {
		connman_error("%s: no device", __func__);
		g_free(sr);
		return NULL;
	}

	sr->index = connman_device_get_index(device);

	gateway = __connman_ipconfig_get_gateway(sr->index);
	if (gateway == NULL) {
		connman_error("%s: no gateway", __func__);
		g_free(sr);
		return NULL;
	}

	sr->gw_addr = inet_addr(gateway);

	sr->dest = g_strdup(ip_addr);

	sr->service = service;

	sr->rtnl.name = g_strdup(ip_addr);
	sr->rtnl.priority = CONNMAN_RTNL_PRIORITY_DEFAULT;
	sr->rtnl.private = sr;
	sr->rtnl.newroute = sticky_newroute;
	sr->rtnl.index = sr->index;

	if (connman_rtnl_register(&sr->rtnl) != 0) {
		connman_error("%s: rtnl register failed", __func__);
		g_free((gpointer)sr->rtnl.name);
		g_free(sr->dest);
		g_free(sr);
		return NULL;
	}

	return sr;
}

static void del_sticky_route(struct sticky_route *sr)
{
	int err;

	_DBG_SERVICE("");

	CONNMAN_ASSERT(sr != NULL);

	connman_rtnl_unregister(&sr->rtnl);
	err = connman_inet_del_hostroute(sr->index, sr->ip_addr, sr->gw_addr);
	if (err < 0)
		connman_warn("%s: del hostroute failed: %d", __func__, err);
	g_free((gpointer)sr->rtnl.name);
	g_free(sr->dest);
	g_free(sr);
}

static DBusMessage *set_property(DBusConnection *conn,
				 DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	DBusMessageIter iter;
	DBusMessage *reply;
	const char *name;

	if (dbus_message_iter_init(msg, &iter) == FALSE)
		return __connman_error_invalid_arguments(msg);

	dbus_message_iter_get_basic(&iter, &name);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	_DBG_SERVICE("service %p property %s", service, name);

	reply = __service_set_property(service, &iter, msg);
	return reply != NULL ?
	    reply : g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static void state_change(struct connman_service *service,
    enum connman_service_state state, int notify)
{
	_DBG_SERVICE("service state change (%p) %s -> %s", service,
		__statename(service->state), __statename(state));
	service->state = state;
	g_get_current_time(&service->last_state[state]);
	if (notify == TRUE)
		__connman_notifier_service_state_changed(service);
}

static void set_idle(struct connman_service *service)
{
	service->error = CONNMAN_SERVICE_ERROR_NO_ERROR;
	state_change(service, CONNMAN_SERVICE_STATE_IDLE, TRUE);
}

static void service_clear_passphrase(struct connman_service *service)
{
	clear_str(&service->passphrase);

	service->wifi.wep_key_idx = 0;		/* close enough */
	service->wifi.wep_key_len = 0;
	memset(&service->wifi.wep_key_matter, 0, MAX_WEP_KEYSPACE);
}

static void __connman_service_clear_passphrase(struct connman_service *service)
{
	connman_bool_t required;

	service_clear_passphrase(service);

	required = is_passphrase_required(service);
	property_changed_boolean(service, "PassphraseRequired", required);
}

static gboolean service_writeback(gpointer arg)
{
	struct connman_service *service = arg;

	g_get_current_time(&service->modified);
	__connman_profile_save_service(service);
	service->writeback_task = 0;
	connman_service_unref(service);
	return FALSE;
}

static void service_writeback_cancel(struct connman_service *service) {
	if (service->writeback_task) {
		g_source_remove(service->writeback_task);
		service->writeback_task = 0;
		connman_service_unref(service);
	}
}

static void service_modified(struct connman_service *service)
{
	if (!service->writeback_task) {
		service->writeback_task =
			g_idle_add(service_writeback,
				   connman_service_ref(service));
	}
}

static void service_recheck_portal_state(struct connman_service *service)
{
	if (is_connected(service) == FALSE)
		return;

	if (__connman_service_check_portal(service) == FALSE) {
		/*
		 * If the proxy configuration has changed and the
		 * service is marked as a portal it is likely because the
		 * portal check was done without using the http proxy.
		 * Mark the service as being online.
		 */
		__connman_service_indicate_state(service,
						 CONNMAN_SERVICE_STATE_ONLINE);
	} else
		__connman_portal_service_recheck_state(service);
}

static void service_clear_eap(struct connman_service *service)
{
	clear_str(&service->eap.identity);
	clear_str(&service->eap.eap);
	clear_str(&service->eap.inner_eap);
	clear_str(&service->eap.anonymous_identity);
	clear_str(&service->eap.client_cert);
	clear_str(&service->eap.cert_id);
	clear_str(&service->eap.private_key);
	clear_str(&service->eap.private_key_passwd);
	clear_str(&service->eap.key_id);
	clear_str(&service->eap.ca_cert);
	clear_str(&service->eap.ca_cert_id);
	clear_str(&service->eap.ca_cert_nss);
	clear_str(&service->eap.pin);
	clear_str(&service->eap.password);

	service->eap.use_system_cas = TRUE;
}

void __connman_service_reset_in_memory(struct connman_service *service)
{
	connman_service_set_favorite(service, FALSE);
	connman_service_set_previously_connected(service, FALSE);
	__connman_service_clear_passphrase(service);
	service_clear_eap(service);

	clear_str(&service->guid);
	clear_str(&service->proxy_config);

	service->pri = CONNMAN_SERVICE_PRI_NONE;
	service->check_portal = CONNMAN_SERVICE_CHECK_PORTAL_AUTO;

	service->last_attempt.tv_sec = 0;
	service->last_attempt.tv_usec = 0;
	/*
	 * NB: we intentionally do not mark the service modified
	 *     as that will cause it to be written to the profile
	 *     which none of the callers want
	 */
}

static DBusMessage *clear_property(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	const char *name;

	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name,
							DBUS_TYPE_INVALID);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	_DBG_SERVICE("service %p property %s", service, name);

	if (g_str_equal(name, "Error") == TRUE) {
		set_idle(service);

		service_modified(service);
	} else if (g_str_equal(name, "Passphrase") == TRUE) {
		__connman_service_clear_passphrase(service);

		service_modified(service);
	} else if (g_str_equal(name, kEAPIdentity) == TRUE) {
		clear_str(&service->eap.identity);

		service_modified(service);
	} else if (g_str_equal(name, kEAPEAP) == TRUE) {
		clear_str(&service->eap.eap);

		service_modified(service);
	} else if (g_str_equal(name, kEAPInnerEAP) == TRUE) {
		clear_str(&service->eap.inner_eap);

		service_modified(service);
	} else if (g_str_equal(name, kEAPAnonymousIdentity) == TRUE) {
		clear_str(&service->eap.anonymous_identity);

		service_modified(service);
	} else if (g_str_equal(name, kEAPClientCert) == TRUE) {
		clear_str(&service->eap.client_cert);

		service_modified(service);
	} else if (g_str_equal(name, kEAPCertID) == TRUE) {
		clear_str(&service->eap.cert_id);

		service_modified(service);
	} else if (g_str_equal(name, kEAPPrivateKey) == TRUE) {
		clear_str(&service->eap.private_key);

		service_modified(service);
	} else if (g_str_equal(name, kEAPPrivateKeyPassword) == TRUE) {
		clear_str(&service->eap.private_key_passwd);

		service_modified(service);
	} else if (g_str_equal(name, kEAPKeyID) == TRUE) {
		clear_str(&service->eap.key_id);

		service_modified(service);
	} else if (g_str_equal(name, kEAPCACert) == TRUE) {
		clear_str(&service->eap.ca_cert);

		service_modified(service);
	} else if (g_str_equal(name, kEAPCACertID) == TRUE) {
		clear_str(&service->eap.ca_cert_id);

		service_modified(service);
	} else if (g_str_equal(name, kEAPCACertNSS) == TRUE) {
		clear_str(&service->eap.ca_cert_nss);

		service_modified(service);
	} else if (g_str_equal(name, kEAPPIN) == TRUE) {
		clear_str(&service->eap.pin);

		service_modified(service);
	} else if (g_str_equal(name, kEAPPassword) == TRUE) {
		clear_str(&service->eap.password);

		service_modified(service);
	} else if (g_str_equal(name, kEAPKeyMgmt) == TRUE) {
		clear_str(&service->eap.key_mgmt);

		service_modified(service);
	} else if (g_str_equal(name, "ProxyConfig") == TRUE) {
		clear_str(&service->proxy_config);

		service_recheck_portal_state(service);
		service_modified(service);
	} else if (g_str_equal(name, "Priority") == TRUE) {
		if (service->pri != CONNMAN_SERVICE_PRI_NONE) {
			service->pri = CONNMAN_SERVICE_PRI_NONE;
			__service_resort(service);

			service_modified(service);
		}
	} else if (g_str_equal(name, "Cellular.APN") == TRUE) {
		DBusMessage *returnmsg = clear_apn(service, msg);
		if (returnmsg != NULL)
			return returnmsg;
		service_modified(service);

	} else if (g_str_equal(name, "CheckPortal") == TRUE) {
		service->check_portal = CONNMAN_SERVICE_CHECK_PORTAL_AUTO;

		service_recheck_portal_state(service);
		service_modified(service);
	} else if (g_str_equal(name, "UIData") == TRUE) {
		clear_str(&service->uidata);

		service_modified(service);
	} else
		return __connman_error_invalid_property(msg);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static connman_bool_t is_connecting(const struct connman_service *service)
{
	return (service->state == CONNMAN_SERVICE_STATE_ASSOCIATION ||
		service->state == CONNMAN_SERVICE_STATE_CONFIGURATION);
}

static connman_bool_t is_connected(const struct connman_service *service)
{
	return (service->state == CONNMAN_SERVICE_STATE_READY ||
		service->state == CONNMAN_SERVICE_STATE_PORTAL ||
		service->state == CONNMAN_SERVICE_STATE_ONLINE);
}

static connman_bool_t is_portal(const struct connman_service *service)
{
	return (service->state == CONNMAN_SERVICE_STATE_PORTAL);
}

static connman_bool_t is_connecting_or_connected(
	const struct connman_service *service)
{
	return (is_connecting(service) || is_connected(service));
}

connman_bool_t __connman_service_get_connected(
	const struct connman_service *service)
{
	if (service == NULL)
		return FALSE;
	return is_connected(service);
}

connman_bool_t __connman_service_get_connecting_or_connected(
	const struct connman_service *service)
{
	if (service == NULL)
		return FALSE;
	return is_connecting_or_connected(service);
}

static connman_bool_t is_ignore(const struct connman_service *service)
{
	if (service->favorite == FALSE) {
		_DBG_SERVICE("SKIP service %s !favorite",
		    service->identifier);
		return TRUE;
	}
	if (service->autoconnect == FALSE) {
		_DBG_SERVICE("SKIP service %s !autoconnect",
		    service->identifier);
		return TRUE;
	}
	if (service->ignore == TRUE) {
		_DBG_SERVICE("SKIP service %s ignore",
		    service->identifier);
		return TRUE;
	}
	if (__connman_service_check_prepared(service) < 0) {
		_DBG_SERVICE("SKIP service %s !prepared",
		    service->identifier);
		return TRUE;
	}
	return FALSE;			/* NB: means good to use */
}

/*
 * Search the services list for an autoconnect candidate and, if found,
 * kick off a connection request.  A device may be specified to restrict
 * selection to only services associated with that device.  Another
 * device can be specified to exclude services associated with a device.
 * The latter is typically used together with the NULL device to scan
 * for any service not associated with a specific device.
 *
 * Return TRUE when the request has "completed": either by issuing a
 * connection request, queueing the request for later handling, or
 * because the request cannot be completed under any circumstances.
 *
 * Algorithm is:
 * 1) if suspending, defer the request
 * 2) if a device is specified and it is no longer present
 *    (has no driver registered) skip the request--this handles
 *    the case where a device is removed from the system before
 *    associated services are reclaimed,
 * 3a)if a device is specified and another service associated with
 *    the device is connected or in the process of connecting, skip
 *    the request
 * 3b)if no device is specified and any service is connected or in
 *    the process of connecting, skip the request
 * 4a)if a device is specified, pick the first service associated
 *    with the device that is enabled for autoconnect and prepared
 * 4b)if no device is specified, pick the first service that is
 *    enabled for autoconnect and prepared
 *
 * NB: The module-global service_list contains an ordered list of
 * services that we can connect to.  They are sorted using the
 * service_compare function every time a service changes state.
 * Therefore we can make some assumptions in the loop below that we
 * will encounter items in the connected, is_connecting(), service priority
 * and favorite order.
 *
 * This work is typically done on a device/service state change from
 * the idle loop (see below).
 */
static connman_bool_t __auto_connect(struct connman_device *device,
    struct connman_device *exclude_device)
{
	struct connman_device *service_device;
	struct connman_service *service;
	GSequenceIter *iter;

	_DBG_SERVICE("suspended %d shutdown %d device %s exclude %s",
	    is_suspended,
            is_shutting_down,
	    device != NULL ?
	        connman_device_get_interface(device) : NULL,
	    exclude_device != NULL ?
	        connman_device_get_interface(exclude_device) : NULL);

	/*
	 * If the system is suspended (suspending/resuming, really, or we
	 * wouldn't be here) or shutting down, defer the auto-connect until
	 * later, when we will hopefully be out of the
	 * is_suspended/is_shutting_down state.  (Defer rather than just
	 * dropping so that the desire-to-autoconnect state is preserved across
	 * a suspend).
	 */
	if (is_suspended == TRUE || is_shutting_down == TRUE) {
		if (g_queue_find(auto_connect_queue, device) == NULL) {
			/* NB: take another ref as the caller drops one */
			g_queue_push_tail(auto_connect_queue,
			    connman_device_ref(device));
			_DBG_SERVICE("%s, queue device",
			             (is_suspended ?
			              "suspending" : "shutting down"));
		}
		return TRUE;		/* request completed */
	}

	if (device != NULL) {
		/*
		 * Confirm that the device wasn't invalidated between the
		 * call to connman_service_auto_connect() and here. If it
		 * was, then drop the request on the floor.
		 */
		if (__connman_device_has_driver(device) == FALSE) {
			_DBG_SERVICE("device is invalid, ignore request");
			return FALSE;
		}
	}

	/*
	 * Make sure no other service is connecting or connected.
	 */
	iter = g_sequence_get_begin_iter(service_list);
	while (g_sequence_iter_is_end(iter) == FALSE) {
		service = g_sequence_get(iter);
		service_device = connman_service_get_device(service);

		if (device != NULL && service_device != device)
			goto next0;

		if (is_connecting_or_connected(service) == TRUE) {
			_DBG_SERVICE("ABORT service %s state %s",
			    service->identifier,
			    connman_service_get_state(service));
			return TRUE;	/* request completed */
		}
	next0:
		iter = g_sequence_iter_next(iter);
	}

	/*
	 * Pick the first service that works and connect.
	 */
	iter = g_sequence_get_begin_iter(service_list);
	while (g_sequence_iter_is_end(iter) == FALSE) {
		service = g_sequence_get(iter);
		service_device = connman_service_get_device(service);

		if (service_device == NULL) {
			if (service->provider == NULL) {
				/* NB: if provision'd we never have a device */
				connman_error("%s: service %s has no device",
				    __func__, service->identifier);
			}
			goto next1;
		}
		if (device != NULL && service_device != device) {
			_DBG_SERVICE("SKIP service %s device %s",
			    service->identifier,
			    connman_device_get_interface(service_device));
			goto next1;
		}

		if (exclude_device != NULL &&
		    service_device == exclude_device) {
			_DBG_SERVICE("EXCLUDE service %s device %s",
			    service->identifier,
			    connman_device_get_interface(service_device));
			goto next1;
		}

		if (service->state != CONNMAN_SERVICE_STATE_IDLE &&
		    service->state != CONNMAN_SERVICE_STATE_FAILURE) {
			_DBG_SERVICE("SKIP service %s state %s",
			    service->identifier,
			    connman_service_get_state(service));
			goto next1;
		}

		if (is_ignore(service) == FALSE) {
			service->userconnect = FALSE;
			_DBG_SERVICE("PICK %s", service->identifier);
			__connman_service_connect(service);
			return TRUE;	/* request completed */
		}
	next1:
		iter = g_sequence_iter_next(iter);
	}
	return FALSE;
}

static gboolean auto_connect(gpointer data)
{
	struct connman_device *device = data;

	CONNMAN_ASSERT(device != NULL);

	_DBG_SERVICE("device %s", connman_device_get_interface(device));

	if (__auto_connect(device, NULL) == FALSE &&
	    __connman_connection_is_online() == FALSE) {
		/*
		 * No service was found for this device and we are currently
		 * offline, try again for any other device other than the
		 * specified device.  This handles the case where the highest
		 * priority device is no longer usable and we want to fallback
		 * to a service associated with another available device.
		 */
		__auto_connect(NULL, device);
	}
	/*
	 * Drop ref added in __connman_service_device_auto_connect.
	 */
	connman_device_unref(device);
	return FALSE;
}

/**
 * Do autoconnect as a deferred operation called from the
 * main event loop. Otherwise, we may be performing a
 * connect operation from some point that's deeply nested
 * in the call stack (e.g., in the middle of handling a
 * disconnect), when all the normal preconditions for
 * initiating a connection may not yet be established.
 */
void __connman_service_device_auto_connect(struct connman_device *device)
{
	CONNMAN_ASSERT(device != NULL);
	/*
	 * Ensure that the device pointer is still valid when
	 * auto_connect runs.
	 */
	g_idle_add(auto_connect, connman_device_ref(device));
}

/*
 * Attempt autoconnect on the device associated with the specified
 * service.  Note this is deferred to the main event loop.
 */
void connman_service_auto_connect(struct connman_service *service)
{
	struct connman_device *device = connman_service_get_device(service);
	/*
	 * This function is often called from connect_timeout which
	 * has a reference to the service but not the device.  While
	 * waiting for the connect operation to timeout the device may
	 * have disappeared.
	 */
	if (device != NULL)
		__connman_service_device_auto_connect(device);
}

static gboolean auto_connect_any(gpointer data)
{
	_DBG_SERVICE("");

	if (__connman_connection_is_online() == FALSE &&
	    __auto_connect(NULL, NULL) == FALSE) {
		/*
		 * No service was found to start; kick off scan requests
		 * on all available devices in case there are networks
		 * available but without an associated service.
		 */
		__connman_element_request_scan(CONNMAN_ELEMENT_TYPE_UNKNOWN);
	}
	return FALSE;
}

/*
 * Attempt autoconnect on any device, but only if offline.
 */
void __connman_service_auto_connect_any(void)
{
	g_idle_add(auto_connect_any, NULL);
}

/**
 * __connman_service_set_activation_state:
 * @service: service structure
 * @activation state: control interface
 * @err: if the activation failed a reason for the failure
 *
 * Set the activation state of the network
 */
void __connman_service_set_activation_state(struct connman_service *service,
	enum connman_network_activation_state activation_state,
	enum connman_element_error err)
{
	const char *state = activation_state2string(activation_state);

	if (err != CONNMAN_ELEMENT_ERROR_NO_ERROR) {
		service->error = __connman_element_convert_error(err);
		__connman_service_indicate_state(
				service,
				CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE);
	}

	service->cellular.activation_state = activation_state;

	property_changed_string(service, "Cellular.ActivationState", state);
}

/**
 * __connman_service_set_operator:
 * @service: service structure
 * @op: operator structure
 *
 * Set operator information for the service
 */
void __connman_service_set_operator(struct connman_service *service,
                                    struct connman_network_operator *op)
{
	connman_bool_t changed = FALSE;

	if (g_strcmp0(service->cellular.serving_operator.name, op->name) != 0) {
		g_free(service->cellular.serving_operator.name);
		service->cellular.serving_operator.name = g_strdup(op->name);
		changed = TRUE;
	}
	if (g_strcmp0(service->cellular.serving_operator.code, op->code) != 0) {
		g_free(service->cellular.serving_operator.code);
		service->cellular.serving_operator.code = g_strdup(op->code);
		changed = TRUE;
	}
	if (g_strcmp0(service->cellular.serving_operator.country,
		      op->country) != 0) {
		g_free(service->cellular.serving_operator.country);
		service->cellular.serving_operator.country =
                        g_strdup(op->country);
		changed = TRUE;
	}

	if (changed) {
		(void) connman_dbus_send_property_changed_string_dict(
				service->path,
				CONNMAN_SERVICE_INTERFACE,
				"Cellular.ServingOperator",
				append_operator,
				&service->cellular.serving_operator);
	}
}

/**
 * __connman_service_set_olp_url:
 * @service: service structure
 * @olp_url: online payment url
 * @olp_method: online payment method (GET/POST)
 * @olp_postdata: online payment data for post
 *
 * Set online payment url for the service
 */
void __connman_service_set_olp_url(struct connman_service *service,
				   const char *olp_url,
				   const char *olp_method,
				   const char *olp_postdata)
{
	connman_bool_t changed = FALSE;
	if (g_strcmp0(service->cellular.olp.url, olp_url) != 0) {
		g_free(service->cellular.olp.url);
		service->cellular.olp.url = g_strdup(olp_url);
		changed = TRUE;

		/* TODO(jglasgow): remove after chrome updated */
		property_changed_string(service, "Cellular.OlpUrl", olp_url);
	}
	if (g_strcmp0(service->cellular.olp.method, olp_method) != 0) {
		g_free(service->cellular.olp.method);
		service->cellular.olp.method = g_strdup(olp_method);
		changed = TRUE;
	}
	if (g_strcmp0(service->cellular.olp.postdata, olp_postdata) != 0) {
		g_free(service->cellular.olp.postdata);
		service->cellular.olp.postdata = g_strdup(olp_postdata);
		changed = TRUE;
	}

	if (changed) {
		(void) connman_dbus_send_property_changed_string_dict(
				service->path,
				CONNMAN_SERVICE_INTERFACE,
				"Cellular.Olp",
				append_online_portal,
				&service->cellular.olp);
	}
}

/**
 * __connman_service_set_usage_url:
 * @service: service structure
 * @usage_url: data usage url
 *
 * Set data usage url for the service
 */
void __connman_service_set_usage_url(struct connman_service *service,
				   const char *usage_url)
{
	if (g_strcmp0(service->cellular.usage_url, usage_url) != 0) {
		g_free(service->cellular.usage_url);
		service->cellular.usage_url = g_strdup(usage_url);
		property_changed_string(service, "Cellular.UsageUrl",
		    usage_url);
	}
}

/**
 * connman_service_set_registration_info:
 * @network: network structure
 * @network_technology: e.g., GPRS, EDGE, HSPA, 1xRTT, EVDO, etc.
 * @roaming_state: e.g., Home, Roaming
 *
 * Set network technology and roaming status information for the service.
 */
void __connman_service_set_registration_info(
		struct connman_service *service,
		enum connman_network_cellular_technology network_technology,
		enum connman_network_cellular_roaming_state roaming_state)
{
	if (network_technology != CONNMAN_NETWORK_TECHNOLOGY_UNKNOWN) {
		if (service->cellular.network_technology != network_technology) {
			property_changed_string(service,
			    "Cellular.NetworkTechnology",
			    network_technology2string(network_technology));
		}
		service->cellular.network_technology = network_technology;
	}
	if (roaming_state != CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN) {
		if (service->cellular.roaming_state != roaming_state) {
			property_changed_string(service,
			    "Cellular.RoamingState",
			    roaming_state2string(roaming_state));
		}
		service->cellular.roaming_state = roaming_state;
	}
}

static gboolean recheck_portaled_services(gpointer arg);

void connman_service_set_connectivity_state(struct connman_service *service,
      enum connman_service_connectivity_state connectivity)
{
	if (!is_connected(service))
		return;

	switch (connectivity) {
	case CONNMAN_SERVICE_CONNECTIVITY_STATE_UNRESTRICTED:
		__connman_service_indicate_state(service,
					CONNMAN_SERVICE_STATE_ONLINE);
		break;
	case CONNMAN_SERVICE_CONNECTIVITY_STATE_RESTRICTED:
		service->error = CONNMAN_SERVICE_ERROR_HTTP_GET_FAILED;
		__connman_service_indicate_state(service,
					CONNMAN_SERVICE_STATE_PORTAL);
		break;
	case CONNMAN_SERVICE_CONNECTIVITY_STATE_NONE:
		service->error = CONNMAN_SERVICE_ERROR_DNS_LOOKUP_FAILED;
		__connman_service_indicate_state(service,
					CONNMAN_SERVICE_STATE_PORTAL);
		break;
	default:
		connman_error("invalid connectivity state %d", connectivity);
	}

	if (is_portal(service) && portal_recheck_timeout == 0)
		portal_recheck_timeout =
			g_timeout_add_seconds(PORTAL_RECHECK_SECS,
					      recheck_portaled_services,
					      NULL);
}

static void remove_timeout(struct connman_service *service)
{
	if (service->timeout > 0) {
		gboolean ret = g_source_remove(service->timeout);
		DBG(DBG_SERVICE | DBG_WIFI, "service %p timeout %d ret %d",
			service, service->timeout, ret);

		service->timeout = 0;
	}
}

static gboolean connect_timeout(gpointer user_data)
{
	struct connman_service *service = user_data;

	if (service->timeout == 0) {
		/*
		 * This should never happen, but it appears to from time
		 * to time.  This implies that glibc's timer function has
		 * called us, even though we have already canceled the
		 * timer!
		 */
		connman_warn("%s: service timer %p called with a zero timer!",
			     __func__, service);
		return FALSE;
	}

	DBG(DBG_SERVICE | DBG_WIFI, "service %p timeout %d",
			service, service->timeout);

	service->timeout = 0;

	connman_service_ref(service);
	/* NB: do before disconnect to get a valid state change */
	__connman_service_indicate_error(service,
					CONNMAN_SERVICE_ERROR_CONNECT_FAILED);

	if (service->network != NULL)
		__connman_network_disconnect(service->network);
	else if (service->device != NULL)
		__connman_device_disconnect(service->device);
	else if (service->provider != NULL)
		__connman_provider_disconnect(service->provider);
	connman_service_unref(service);

	return FALSE;
}

static void set_reconnect_state(struct connman_service *service,
	connman_bool_t onoff)
{
	struct connman_device *device;

	device = connman_service_get_device(service);
	if (device != NULL)
		__connman_device_set_reconnect(device, onoff);
}

static connman_bool_t get_reconnect_state(struct connman_service *service)
{
	struct connman_device *device;

	device = connman_service_get_device(service);
	return device != NULL ? __connman_device_get_reconnect(device) : FALSE;
}

static struct connman_service *get_connecting_or_connected_service(
	enum connman_service_type type)
{
	GSequenceIter *iter = g_sequence_get_begin_iter(service_list);

	_DBG_SERVICE("type %s", service_type2string(type));

	while (g_sequence_iter_is_end(iter) == FALSE) {
		struct connman_service *service = g_sequence_get(iter);

		_DBG_SERVICE("service %p type %s state %s", service,
		    service_type2string(service->type),
		    __statename(service->state));

		if (service->type == type &&
		    is_connecting_or_connected(service) == TRUE)
			return service;
		iter = g_sequence_iter_next(iter);
	}
	return NULL;
}

static DBusMessage *connect_service(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	struct connman_service *temp;
	int err;

	_DBG_SERVICE("service %p", service);

	temp = get_connecting_or_connected_service(service->type);
	if (temp != NULL) {
		/*
		 * A service is already connecting or connected.  If it
		 * is the requested service then let it continue and
		 * notify the caller.  Otherwise we need to shut down the
		 * other connection attempt and start this new one.
		 * Disconnect of the other service should be synchronous
		 * (from our POV); bail if not as we're not prepared to
		 * block here.
		 */
		if (temp == service) {
			if (is_connected(service))
				return __connman_error_already_connected(msg);
			else
				return __connman_error_in_progress(msg);
		}
		err = __connman_service_disconnect(temp);
		if (err < 0 && err != -ENOTCONN && err != -EALREADY) {
			/* NB: this should not happen */
			connman_error("%s: cannot disconnect service (err %d)",
			    __func__, err);
			/* TODO(sleffler) find better error? */
			return __connman_error_operation_aborted(msg);
		}
	} else if (service->type != CONNMAN_SERVICE_TYPE_VPN) {
		struct connman_device *device;

		/*
		 * No in-progress connect request but the device may
		 * still be connected.  Force disconnect to be sure.
		 * Note that we disable auto-reconnect first so the
		 * disconnect doesn't cause us to immediately startup
		 * a reconnection.
		 */
		device = connman_service_get_device(service);
		if (device == NULL) {
			connman_error("%s: no device found", __func__);
			return __connman_error_invalid_service(msg);
		}
		__connman_device_set_reconnect(device, FALSE);
		__connman_device_disconnect(device);
	}

	service->ignore = FALSE;
	service->userconnect = TRUE;
	set_idle(service);			/* NB: clear previous failure */
	set_reconnect_state(service, FALSE);

	err = __connman_service_connect(service);
	if (err < 0 && err != -EINPROGRESS)
		return __connman_error_failed(msg, -err);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *disconnect_service(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	int err;

	_DBG_SERVICE("service %p", service);

	if (is_connecting_or_connected(service)) {
		service->ignore = TRUE;
		set_reconnect_state(service, FALSE);

		err = __connman_service_disconnect(service);
		if (err < 0 && err != -EINPROGRESS)
			return __connman_error_failed(msg, -err);
	}
	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *remove_service(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;

	_DBG_SERVICE("service %p", service);

	if (service->type == CONNMAN_SERVICE_TYPE_ETHERNET)
		return __connman_error_not_supported(msg);

	if (service->favorite == FALSE &&
	    service->type != CONNMAN_SERVICE_TYPE_VPN) {
		/* NB: vpn services are never marked favorite */
		return __connman_error_not_supported(msg);
	}

	set_reconnect_state(service, FALSE);

	/*
	 * network_disconnect may drop the last reference to the service and
	 * cause it to be freed, so take a reference temporarily until we are
	 * done with it.
	 */
	connman_service_ref(service);
	__connman_service_disconnect(service);
	__connman_service_reset_in_memory(service);
	if (service->provider != NULL) {
		/*
		 * Drop the reference created by Manager.GetService
		 * so the service goes away below on last unref.
		 */
		connman_service_unref(service);
	}

	/* Nuke any writeback tasks that may still be pending. */
	service_writeback_cancel(service);

	/*
	 * Log a warning if there are still service references. This is bad
	 * because the service is now in half-dead state and requests on it such
	 * as listing all services might bring it back from the dead
	 */
	if (service->refcount > 1) {
		connman_warn("remove_service: Service %s refcount %d\n",
			     service->path, service->refcount);
	}

	connman_service_unref(service);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *move_before(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	struct connman_service *target;
	const char *path;
	GSequenceIter *src, *dst;

	_DBG_SERVICE("service %p", service);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);

	if (service->favorite == FALSE)
		return __connman_error_not_supported(msg);

	target = find_service(path);
	if (target == NULL || target->favorite == FALSE || target == service)
		return __connman_error_invalid_service(msg);

	_DBG_SERVICE("target %s", target->identifier);

	if (target->state != service->state)
		return __connman_error_invalid_service(msg);

	service_modified(service);

	src = g_hash_table_lookup(service_hash, service->identifier);
	dst = g_hash_table_lookup(service_hash, target->identifier);

	g_sequence_move(src, dst);

	__connman_profile_changed(service->profile, FALSE);

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *move_after(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	struct connman_service *target;
	const char *path;

	_DBG_SERVICE("service %p", service);

	dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);

	if (service->favorite == FALSE)
		return __connman_error_not_supported(msg);

	target = find_service(path);
	if (target == NULL || target->favorite == FALSE || target == service)
		return __connman_error_invalid_service(msg);

	_DBG_SERVICE("target %s", target->identifier);

	if (target->state != service->state)
		return __connman_error_invalid_service(msg);

	service_modified(service);

	return __connman_error_not_implemented(msg);
}

static DBusMessage *activate_cellular_modem(DBusConnection *conn,
    DBusMessage *msg, void *user_data)
{
	struct connman_service *service = user_data;
	struct connman_network *network = service->network;
	const char *carrier;
	int err;

	_DBG_SERVICE("conn %p", service);

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_MODIFY) < 0)
		return __connman_error_permission_denied(msg);

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &carrier,
	    DBUS_TYPE_INVALID) == FALSE)
		return __connman_error_invalid_arguments(msg);


	if (network != NULL)
		err = __connman_network_activate_cellular_modem(network,
								carrier);
	else
		err = -EINVAL;

	switch (err) {
	case 0:
		return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
	case -EINPROGRESS:
		/* expected */
		return __connman_error_in_progress(msg);
	case -EINVAL:
		return __connman_error_not_supported(msg);
	default:
		return __connman_error_failed(msg, -err);
	}
}

static GDBusMethodTable service_methods[] = {
	{ "GetProperties", "",   "a{sv}", get_properties     },
	{ "SetProperty",   "sv", "",      set_property       },
	{ "ClearProperty", "s",  "",      clear_property     },
	{ "Connect",       "",   "",      connect_service,   },
	{ "Disconnect",    "",   "",      disconnect_service },
	{ "Remove",        "",   "",      remove_service     },
	{ "MoveBefore",    "o",  "",      move_before        },
	{ "MoveAfter",     "o",  "",      move_after         },
	{ "ActivateCellularModem",
			   "s",  "",      activate_cellular_modem},
	{ },
};

static GDBusSignalTable service_signals[] = {
	{ "PropertyChanged", "sv" },
	{ },
};

static void service_free(gpointer user_data)
{
	struct connman_service *service = user_data;
	char *path = service->path;

	_DBG_SERVICE("service %p", service);

	if (service->in_watchlist == TRUE)
		connman_service_notify_watchlist(CONNMAN_SERVICE_STATE_UNKNOWN);

	remove_timeout(service);

	if (service->writeback_task) {
		(void) service_writeback(service);
                service_writeback_cancel(service);
	}

	g_hash_table_remove(service_hash, service->identifier);
	if (service->guid != NULL)
		g_hash_table_remove(service_hash, service->guid);

	service->path = NULL;

	if (path != NULL) {
		__connman_profile_changed(service->profile, FALSE);

		g_dbus_unregister_interface(connection, path,
						CONNMAN_SERVICE_INTERFACE);
		g_free(path);
	}

	if (service->network != NULL)
		connman_network_unref(service->network);
	if (service->provider != NULL)
		__connman_provider_remove(service->provider);
	if (service->sticky_route != NULL)
		del_sticky_route(service->sticky_route);

	g_free(service->name);
	g_free(service->guid);
	g_free(service->passphrase);
	g_free(service->eap.identity);
	g_free(service->eap.eap);
	g_free(service->eap.inner_eap);
	g_free(service->eap.anonymous_identity);
	g_free(service->eap.client_cert);
	g_free(service->eap.cert_id);
	g_free(service->eap.private_key);
	g_free(service->eap.private_key_passwd);
	g_free(service->eap.key_id);
	g_free(service->eap.ca_cert);
	g_free(service->eap.ca_cert_id);
	g_free(service->eap.ca_cert_nss);
	g_free(service->eap.pin);
	g_free(service->eap.password);
	g_free(service->identifier);
	g_free(service->cellular.serving_operator.name);
	g_free(service->cellular.serving_operator.country);
	g_free(service->cellular.serving_operator.code);
	g_free(service->cellular.olp.url);
	g_free(service->cellular.olp.method);
	g_free(service->cellular.olp.postdata);
	g_free(service->cellular.usage_url);
	g_free(service->cellular.apn.apn);
	g_free(service->cellular.apn.network_id);
	g_free(service->cellular.apn.username);
	g_free(service->cellular.apn.password);
	g_free(service->cellular.last_good_apn.apn);
	g_free(service->cellular.last_good_apn.network_id);
	g_free(service->cellular.last_good_apn.username);
	g_free(service->cellular.last_good_apn.password);
	g_free(service->wifi.hex_ssid);
	g_free(service);
}

/**
 * __connman_service_put:
 * @service: service structure
 *
 * Release service if no longer needed
 */
void __connman_service_put(struct connman_service *service)
{
	_DBG_SERVICE("service %p refcount %d", service,
		g_atomic_int_get(&service->refcount) - 1);

	CONNMAN_ASSERT(service->refcount > 0);
	if (g_atomic_int_dec_and_test(&service->refcount) == TRUE) {
		GSequenceIter *iter;

		iter = g_hash_table_lookup(service_hash, service->identifier);
		if (iter != NULL) {
			__connman_service_disconnect(service);

			/* NB: this implicitly calls service_free */
			g_sequence_remove(iter);
		} else
			service_free(service);
	}
}

static void __connman_service_initialize(struct connman_service *service)
{
	_DBG_SERVICE("service %p", service);

	service->refcount = 1;

	service->pri	  = CONNMAN_SERVICE_PRI_NONE;
	service->type     = CONNMAN_SERVICE_TYPE_UNKNOWN;
	service->mode     = CONNMAN_SERVICE_MODE_UNKNOWN;
	service->security = CONNMAN_SERVICE_SECURITY_UNKNOWN;
	service->state    = CONNMAN_SERVICE_STATE_UNKNOWN;

	service->favorite = FALSE;
	service->previously_connected = FALSE;
	service->hidden = FALSE;
	service->save_credentials = TRUE;
	service->save_to_profile = TRUE;
	service->check_portal = CONNMAN_SERVICE_CHECK_PORTAL_AUTO;

	service->ignore = FALSE;

	service->eap.use_system_cas = TRUE;
	service->userconnect = FALSE;
	service->scangen = -1;
}

/**
 * connman_service_create:
 *
 * Allocate a new service.
 *
 * Returns: a newly-allocated #connman_service structure
 */
struct connman_service *connman_service_create(void)
{
	struct connman_service *service;

	service = g_try_new0(struct connman_service, 1);
	if (service == NULL)
		return NULL;

	_DBG_SERVICE("service %p", service);

	__connman_service_initialize(service);

	return service;
}

/**
 * connman_service_ref:
 * @service: service structure
 *
 * Increase reference counter of service
 */
struct connman_service *connman_service_ref(struct connman_service *service)
{
	g_atomic_int_inc(&service->refcount);

	return service;
}

/**
 * connman_service_unref:
 * @service: service structure
 *
 * Decrease reference counter of service
 */
void connman_service_unref(struct connman_service *service)
{
	__connman_service_put(service);
}

/**
 * connman_service_get_identifier:
 * @service: service structure
 *
 * Return service identifier.
 */
const char *connman_service_get_identifier(const struct connman_service *service)
{
	return service->identifier;
}

/**
 * connman_service_get_autoconnect:
 * @service: service structure
 *
 * Return autoconnect setting.
 */
connman_bool_t connman_service_get_autoconnect(const struct connman_service *service)
{
	return service->autoconnect;
}

/*
 * Return a string specifying service priority ordering.
 * The string must be free'd with g_free.
 */
char *__connman_service_get_ordering(void)
{
	const char *s[CONNMAN_SERVICE_TYPE_MAX];
	int i, j;

	/*
	 * Load services into s ordered by priority;
	 * this assumes each has a distict priority.
	 */
	memset(s, 0, sizeof(s));
	for (i = 0; i < CONNMAN_SERVICE_TYPE_MAX; i++) {
		CONNMAN_ASSERT(service_priority[i] < CONNMAN_SERVICE_TYPE_MAX);
		s[service_priority[i]] = service_type2string(i);
	}

	/* compact array for g_strjoinv */
	j = 0;
	for (i = 0; i < CONNMAN_SERVICE_TYPE_MAX; i++)
		if (s[i] != NULL)
			s[j++] = s[i];
	s[j] = NULL;
	return g_strjoinv(",", (gchar **)s);
}

/*
 * Parse a string specifying service priority ordering and
 * install if there are no syntax errors.  Returns 0 on success
 * and <0 on failure (unique errno to identify reason).
 */
int __connman_service_set_ordering(const char *str)
{
	int prio[CONNMAN_SERVICE_TYPE_MAX];
	gchar **list;
	int i;

	for (i = 0; i < CONNMAN_SERVICE_TYPE_MAX; i++)
		prio[i] = CONNMAN_SERVICE_TYPE_MAX-1;
	/* NB: ","-separated list of service types */
	list = g_strsplit(str, ",", CONNMAN_SERVICE_TYPE_MAX);
	for (i = 0; list[i] != NULL; i++) {
		/* TODO(sleffler) allow prio w/ syntax like wifi=3 */
		enum connman_service_type type =
		    __connman_service_string2type(list[i]);
		if (type == CONNMAN_SERVICE_TYPE_UNKNOWN)
			return -EINVAL;
		prio[type] = i;
	}
	g_strfreev(list);

	/* install new service priorites */
	memcpy(service_priority, prio, sizeof(prio));
	return 0;
}

/*
 *  Compare two services for the purpose of ordering by preference.
 *  1. Prefer connected service to !connected
 *  2. Prefer different services according to service priority
 *     (default: wired Ethernet > Bluetooth > WiFi > WiMax > Cellular)
 *  3. Prefer favorites to !favorites
 *  4. Prefer services by security
 *  4. Prefer service with best signal strength (XXX meaningless for Ethernet)
 *
 *  Return: a > b	-1	(a before b)
 *  	    b == a	0
 *  	    a < b	+1	(a after b)
 */
static gint service_compare(gconstpointer a, gconstpointer b,
							gpointer user_data)
{
	const struct connman_service *service_a = (const void *) a;
	const struct connman_service *service_b = (const void *) b;
	int prio_a, prio_b, sec_a, sec_b;
	connman_bool_t connecting_a, connecting_b, connected_a, connected_b;
	connman_bool_t portal_a, portal_b;

	/*
	 * TODO(ers): sort anything with service->active == TRUE higher
	 * than anything else?
	 */
	if (service_a->state != service_b->state) {
		connected_a = is_connected(service_a);
		connected_b = is_connected(service_b);

		if (connected_a == TRUE && connected_b == FALSE)
			return -1;
		if (connected_a == FALSE && connected_b == TRUE)
			return 1;

		portal_a = is_portal(service_a);
		portal_b = is_portal(service_b);
		if (portal_a == TRUE && portal_b == FALSE)
			return 1;
		if (portal_a == FALSE && portal_b == TRUE)
			return -1;

		connecting_a = is_connecting(service_a);
		connecting_b = is_connecting(service_b);
		if (connecting_a == TRUE && connecting_b == FALSE)
			return -1;
		if (connecting_a == FALSE && connecting_b == TRUE)
			return 1;
	}

	/* NB: favorite only applies when comparing services of the same type */
	/* TODO(sleffler): "favorite" should contribute but not be decisive */
	if (service_a->favorite == TRUE && service_b->favorite == FALSE)
		return -1;
	if (service_a->favorite == FALSE && service_b->favorite == TRUE)
		return 1;

	/* failed services should be sorted below non-failed services */
	if (service_a->state != CONNMAN_SERVICE_STATE_FAILURE &&
	    service_b->state == CONNMAN_SERVICE_STATE_FAILURE)
		return -1;
	if (service_a->state == CONNMAN_SERVICE_STATE_FAILURE &&
	    service_b->state != CONNMAN_SERVICE_STATE_FAILURE)
		return 1;

	/* assigned priorities take precedence over anything we infer */
	if (service_a->pri != service_b->pri)
		return (service_b->pri - service_a->pri);

	prio_a = service_priority[service_a->type];
	prio_b = service_priority[service_b->type];
	if (prio_a != prio_b) {
		/*
		 * TODO(sleffler): too simplistic, assumes services operate
		 * equally well
		 */
		return prio_a < prio_b ? -1 : 1;
	}

	sec_a = security_priority[service_a->security];
	sec_b = security_priority[service_b->security];
	if (sec_a != sec_b) {
		/*
		 * TODO(sleffler): too simplistic, assumes signal strength
		 * is not important.
		 */
		return sec_a < sec_b ? -1 : 1;
	}

	/* XXX what does this mean for Ethernet? */
	return (gint) service_b->strength - (gint) service_a->strength;
}

static void __service_resort(struct connman_service *service)
{
	GSequenceIter *iter;

	iter = g_hash_table_lookup(service_hash, service->identifier);
	if (iter != NULL)
		g_sequence_sort_changed(iter, service_compare, NULL);
}

/**
 * connman_service_get_favorite:
 * @service: service structure
 *
 * Return the favorite setting for the service.
 */
connman_bool_t connman_service_get_favorite(const struct connman_service *service)
{
	return service->favorite;
}

/**
 * connman_service_set_favorite:
 * @service: service structure
 * @favorite: favorite value
 *
 * Change the favorite setting of service
 */
int connman_service_set_favorite(struct connman_service *service,
						connman_bool_t favorite)
{
	GSequenceIter *iter;

	iter = g_hash_table_lookup(service_hash, service->identifier);
	if (iter == NULL)
		return -ENOENT;

	if (service->favorite == favorite)
		return -EALREADY;

	service->favorite = favorite;

	g_sequence_sort_changed(iter, service_compare, NULL);

	__connman_profile_changed(service->profile, FALSE);

	return 0;
}

/**
 * connman_service_get_previously_connected:
 * @service: service structure
 *
 * Return the previously_connected setting for the service.
 */
connman_bool_t connman_service_get_previously_connected(
					const struct connman_service *service)
{
	return service->previously_connected;
}

/**
 * connman_service_set_previously_connected:
 * @service: service structure
 * @previously_connected: previously_connected value
 *
 * Change the previously_connected setting of service
 */
int connman_service_set_previously_connected(
					struct connman_service *service,
					connman_bool_t previously_connected)
{
	GSequenceIter *iter;

	iter = g_hash_table_lookup(service_hash, service->identifier);
	if (iter == NULL)
		return -ENOENT;

	if (service->previously_connected == previously_connected)
		return -EALREADY;

	service->previously_connected = previously_connected;

	return 0;
}

/**
 * connman_service_get_device:
 * @service: service structure
 *
 * Return device for specified service.
 */
struct connman_device *connman_service_get_device(const struct connman_service *service)
{
	if (service->device != NULL)
		return service->device;
	else if (service->network != NULL)
		return connman_network_get_device(service->network);

	return NULL;
}

/**
 * connman_service_get_interface:
 * @service: service structure
 *
 * Return device interface name for specified service.
 */
const char *connman_service_get_interface(const struct connman_service *service)
{
	if (service->device != NULL) {
		return connman_device_get_interface(service->device);
	} else if (service->network != NULL) {
		struct connman_device *device =
		    connman_network_get_device(service->network);
		if (device != NULL)
			return connman_device_get_interface(device);
	} else if (service->provider != NULL)
		return connman_provider_get_interface(service->provider);

	return NULL;
}

static void service_system_suspend(void)
{
	_DBG_SERVICE("");
	is_suspended = TRUE;
}

static void service_system_resume(void)
{
	_DBG_SERVICE("");
	is_suspended = FALSE;

	/* Run any pending auto-connect actions */
	while (g_queue_is_empty(auto_connect_queue) == FALSE) {
		struct connman_device *device =
			g_queue_pop_head(auto_connect_queue);
		auto_connect(device);
	}

}

static void service_system_shutdown(void)
{
	_DBG_SERVICE("");
	is_shutting_down = TRUE;
}

/*
 * Clear security credentials of a service; used when
 * SaveCredentials is FALSE.
 *
 * NB: don't generate PropertyChanged signal(s)
 */
static void clear_credentials(struct connman_service *service)
{
	service_clear_passphrase(service);

	clear_str(&service->eap.identity);
	clear_str(&service->eap.anonymous_identity);
	clear_str(&service->eap.client_cert);
	clear_str(&service->eap.cert_id);
	clear_str(&service->eap.private_key);
	clear_str(&service->eap.private_key_passwd);
	clear_str(&service->eap.key_id);
	clear_str(&service->eap.pin);
	clear_str(&service->eap.password);
}

static void active_service_changed(void)
{
	__connman_notifier_default_changed(get_active_service());
}

void __connman_service_restart_connect_timer(struct connman_service *service)
{
	if (service->timeout > 0) {
		_DBG_SERVICE("Restarting connect timer");
		remove_timeout(service);
		service->timeout = g_timeout_add_seconds(CONNECT_TIMEOUT_SECS,
							 connect_timeout, service);
	} else {
		connman_info("%s:%s() Connect timer restart request while "
			     "timer is not active", __FILE__, __FUNCTION__);
	}
}

int __connman_service_indicate_state(struct connman_service *service,
					enum connman_service_state state)
{
	if (service == NULL)        /* TODO(sleffler) fix these cases */
		return -EINVAL;

	_DBG_SERVICE("service %p %s -> %s", service,
        __statename(service->state), __statename(state));

	if (service->state == state)
		return -EALREADY;

	if (service->state == CONNMAN_SERVICE_STATE_FAILURE &&
				state == CONNMAN_SERVICE_STATE_IDLE)
		return -EINVAL;

	if (service->state == CONNMAN_SERVICE_STATE_IDLE &&
				state == CONNMAN_SERVICE_STATE_DISCONNECT)
		return -EINVAL;

	if (state == CONNMAN_SERVICE_STATE_IDLE &&
			service->state != CONNMAN_SERVICE_STATE_DISCONNECT) {
		state_change(service, CONNMAN_SERVICE_STATE_DISCONNECT, TRUE);

		__connman_service_disconnect(service);
	}

	if (is_watchlist_state(state) != is_watchlist_state(service->state))
		connman_service_notify_watchlist(state);

	state_change(service, state, TRUE);

	__service_resort(service);
	__connman_connection_resort();

	if (service->security != CONNMAN_SERVICE_SECURITY_NONE &&
	    service->save_credentials == FALSE) {
		/*
		 * Clear security credentials if we've reached a state
		 * where we're done using anything currently configured.
		 */
		if (state == CONNMAN_SERVICE_STATE_IDLE ||
		    state == CONNMAN_SERVICE_STATE_FAILURE ||
		    state == CONNMAN_SERVICE_STATE_ASSOCIATION ||
		    state == CONNMAN_SERVICE_STATE_CONFIGURATION)
			clear_credentials(service);
	}

	if (state == CONNMAN_SERVICE_STATE_IDLE) {
		if (get_reconnect_state(service) == TRUE)
			connman_service_auto_connect(service);
	}

	if (state == CONNMAN_SERVICE_STATE_READY) {
		set_reconnect_state(service, TRUE);

		connman_service_set_favorite(service, TRUE);
		connman_service_set_previously_connected(service, TRUE);

		remove_timeout(service);

		service->userconnect = FALSE;

		service_modified(service);

		__connman_notifier_connect(service->type);
	} else if (state == CONNMAN_SERVICE_STATE_DISCONNECT) {
		active_service_changed();

		__connman_notifier_disconnect(service->type);
	} else if (service->state == CONNMAN_SERVICE_STATE_ASSOCIATION) {
		/* TODO(sleffler) does not handle ethernet */
		g_get_current_time(&service->last_attempt);
	}

	if (state == CONNMAN_SERVICE_STATE_FAILURE) {
		remove_timeout(service);

		if (service->userconnect == FALSE)
			connman_service_auto_connect(service);

		service_modified(service);
	} else if (state != CONNMAN_SERVICE_STATE_ACTIVATION_FAILURE &&
		   state != CONNMAN_SERVICE_STATE_PORTAL)
		service->error = CONNMAN_SERVICE_ERROR_NO_ERROR;

	__connman_profile_changed(service->profile, FALSE);

	if (state == CONNMAN_SERVICE_STATE_DISCONNECT) {
		struct connman_service *act_service = get_active_service();

		/* TODO(sleffler) this looks wrong */
		if (__connman_connection_is_online() == FALSE &&
		    act_service != NULL && act_service->provider != NULL)
		    	__connman_provider_disconnect(act_service->provider);
	}

	if (service->state == CONNMAN_SERVICE_STATE_IDLE ||
			service->state == CONNMAN_SERVICE_STATE_FAILURE)
		__connman_element_request_scan(CONNMAN_ELEMENT_TYPE_UNKNOWN);

	return 0;
}

int __connman_service_indicate_error(struct connman_service *service,
					enum connman_service_error error)
{
	_DBG_SERVICE("service %p error %d", service, error);

	if (service == NULL)
		return -EINVAL;

	service->error = error;

	return __connman_service_indicate_state(service,
					CONNMAN_SERVICE_STATE_FAILURE);
}

int __connman_service_indicate_active(struct connman_service *service,
				      connman_bool_t is_active)
{
	connman_bool_t was_active = service->active;

	_DBG_SERVICE("service %s is_active=%d", identifier(service), is_active);

	service->active = is_active;
	__service_resort(service);

	if (was_active != is_active) {
		if (is_active)
			active_service_changed();
		property_changed_boolean(service, "IsActive", service->active);
	}
	return 0;
}

connman_bool_t __connman_service_check_prepared(const struct connman_service
    *service)
{
	struct connman_device *device;

	switch (service->type) {
	case CONNMAN_SERVICE_TYPE_UNKNOWN:
		_DBG_SERVICE("service %p bad type", service);
		return -EINVAL;
	case CONNMAN_SERVICE_TYPE_ETHERNET:
	case CONNMAN_SERVICE_TYPE_WIMAX:
	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
	case CONNMAN_SERVICE_TYPE_VPN:
		break;
	case CONNMAN_SERVICE_TYPE_CELLULAR:
		device = connman_service_get_device(service);
		if (device != NULL &&
		    connman_device_roaming_allowed(device) == FALSE &&
		    service->cellular.roaming_state == CONNMAN_NETWORK_ROAMING_STATE_ROAMING)
			return -ENONET;
		break;
	case CONNMAN_SERVICE_TYPE_WIFI:
		switch (service->security) {
		case CONNMAN_SERVICE_SECURITY_UNKNOWN:
		case CONNMAN_SERVICE_SECURITY_NONE:
			break;
		case CONNMAN_SERVICE_SECURITY_WEP:
			if (is_key_mgmt_1x(service->eap.key_mgmt) == TRUE) {
				if (is_connectable(service) == FALSE)
					return -ENOKEY;
				break;
			}
			/* fall thru... */
		case CONNMAN_SERVICE_SECURITY_WPA:
		case CONNMAN_SERVICE_SECURITY_RSN:
		case CONNMAN_SERVICE_SECURITY_PSK:
			if (service->passphrase == NULL) {
				_DBG_SERVICE("service %p missing passphrase",
				    service);
				return -ENOKEY;
			}
			/* TODO(sleffler) validate wep key matter? */
			break;
		case CONNMAN_SERVICE_SECURITY_802_1X:
			if (is_connectable(service) == FALSE)
				return -ENOKEY;
			break;
		default:
			break;
		}
		break;
	default:
		break;
	}
	return 0;
}

static connman_bool_t prepare_network(struct connman_service *service)
{
	enum connman_network_type type;
	unsigned int ssid_len;

	type = connman_network_get_type(service->network);

	switch (type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
		return FALSE;
	case CONNMAN_NETWORK_TYPE_WIFI:
		if (connman_network_get_blob(service->network, "WiFi.SSID",
							&ssid_len) == NULL)
			return FALSE;

		prepare_network_passphrase(service);
		prepare_network_eap(service);
		break;
	case CONNMAN_NETWORK_TYPE_WIMAX:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
	case CONNMAN_NETWORK_TYPE_CELLULAR:
		break;
	}

	return TRUE;
}

int __connman_service_connect(struct connman_service *service)
{
	int err;

	_DBG_SERVICE("service %p", service);

	if (is_connected(service) == TRUE)
		return -EISCONN;

	if (is_connecting(service) == TRUE) {
		_DBG_SERVICE("is connecting, state %s (%d)",
		    __statename(service->state), service->state);
		return -EALREADY;
	}

	err = __connman_service_check_prepared(service);
	if (err < 0)
		return err;

	if (service->network != NULL) {
		if (prepare_network(service) == FALSE)
			return -EINVAL;

		err = __connman_network_connect(service->network);
	} else if (service->provider != NULL) {
	    	err = __connman_provider_connect(service->provider);
	} else if (service->device != NULL) {
		if (service->favorite == FALSE)
			return -ENOLINK;

		err = __connman_device_connect(service->device);
	} else
		return -EOPNOTSUPP;

	if (err < 0) {
		if (err != -EINPROGRESS)
			return err;

		service->timeout = g_timeout_add_seconds(CONNECT_TIMEOUT_SECS,
						connect_timeout, service);

		DBG(DBG_SERVICE | DBG_WIFI, "service %p add %d sec timeout %d",
			service, CONNECT_TIMEOUT_SECS, service->timeout);

		return -EINPROGRESS;
	}

	return 0;
}

int __connman_service_disconnect(struct connman_service *service)
{
	_DBG_SERVICE("service %p", service);

	remove_timeout(service);

	if (service->network != NULL) {
		return __connman_network_disconnect(service->network);
	} else if (service->provider != NULL) {
	    	return __connman_provider_disconnect(service->provider);
	} else if (service->device != NULL) {
		if (service->favorite == FALSE)
			return -ENOLINK;
		return __connman_device_disconnect(service->device);
	} else
		return -EOPNOTSUPP;
}

/**
 * __connman_service_lookup:
 * @identifier: service identifier
 *
 * Look up a service by identifier (reference count will not be increased)
 */
struct connman_service *__connman_service_lookup(const char *identifier)
{
	GSequenceIter *iter;

	iter = g_hash_table_lookup(service_hash, identifier);
	if (iter != NULL)
		return g_sequence_get(iter);

	return NULL;
}

static struct connman_network *create_hidden_wifi(struct connman_device *device,
		const char *ssid, const unsigned int ssid_len, const char *mode,
		const char *security)
{
	struct connman_network *network;
	char *name;
	int index;

	if (ssid_len < 1)
		return NULL;

	network = connman_network_create(NULL, CONNMAN_NETWORK_TYPE_WIFI);
	if (network == NULL)
		return NULL;

	connman_network_set_blob(network, "WiFi.SSID",
					(unsigned char *) ssid, ssid_len);

	connman_network_set_string(network, "WiFi.Mode", mode);
	connman_network_set_string(network, "WiFi.Security", security);

	name = connman_network_get_name_from_id((unsigned char *)ssid, ssid_len,
						NULL, 0);
	if (name == NULL) {
		connman_network_unref(network);
		return NULL;
	}

	connman_network_set_name(network, name);

	g_free(name);

	index = connman_device_get_index(device);
	connman_network_set_index(network, index);

	connman_network_set_protocol(network, CONNMAN_NETWORK_PROTOCOL_IP);

	if (connman_device_add_network(device, network) < 0) {
		connman_network_unref(network);
		return NULL;
	}

	connman_network_set_available(network, TRUE);

	return network;
}

static inline gboolean ispsk(const char *security)
{
	return (g_strcmp0(security, "wpa") == 0 ||
		g_strcmp0(security, "rsn") == 0 ||
		g_strcmp0(security, "psk") == 0);
}

/*
 * Common logic to set a service property.  This is used both
 * when creating/configuring services (e.g. with Manager.ConfigureService)
 * and when setting individual properties with Service.SetProperty.
 */
static DBusMessage *__service_set_property(struct connman_service *service,
    DBusMessageIter *entry, DBusMessage *msg)
{
	const char *key, *str;
	DBusMessageIter value;
	DBusMessage *reply;
	int type;

	dbus_message_iter_get_basic(entry, &key);

	dbus_message_iter_next(entry);
	dbus_message_iter_recurse(entry, &value);

	_DBG_SERVICE("service %p property %s", service, key);

	type = dbus_message_iter_get_arg_type(&value);

	reply = NULL;
	if (g_str_equal(key, "Type") == TRUE) {
		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		/*
		 * Cannot change the service type after it is created.
		 * We could validate the current type but this would
		 * break use of this routine by Manager.GetVPNService
		 * with little benefit, so just ignore the it.
		 */
	} else if (g_str_equal(key, "Security") == TRUE) {
		enum connman_service_security security;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &str);
		security = convert_wifi_security(str);
		if (security == CONNMAN_SERVICE_SECURITY_UNKNOWN) {
			connman_error("%s: invalid %s %s", __func__, key, str);
			return __connman_error_invalid_arguments(msg);
		}

		if (service->security != security) {
			/* TODO(sleffler) re-validate credentials? */
			service->security = security;
			/* property changed signal? */
		}
	} else if (g_str_equal(key, "WiFi.Mode") == TRUE ||
		   g_str_equal(key, "Mode") == TRUE) {
		enum connman_service_mode mode;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		if (service->type != CONNMAN_SERVICE_TYPE_WIFI)
			return __connman_error_invalid_service(msg);

		dbus_message_iter_get_basic(&value, &str);
		mode = convert_wifi_mode(str);
		if (mode == CONNMAN_SERVICE_MODE_UNKNOWN) {
			connman_error("%s: invalid %s %s", __func__, key, str);
			return __connman_error_invalid_arguments(msg);
		}

		if (service->mode != mode) {
			service->mode = mode;
			/* property changed signal? */
		}
	} else if (g_str_equal(key, "WiFi.SSID") == TRUE ||
		   g_str_equal(key, "SSID") == TRUE) {
		const char *ssid;
		int ssid_len;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);
		if (service->type != CONNMAN_SERVICE_TYPE_WIFI)
			return __connman_error_invalid_service(msg);

		dbus_message_iter_get_basic(&value, &ssid);
		ssid_len = strlen(ssid);
		if (ssid_len < 1 || ssid_len > MAX_SSID_LENGTH) {
			connman_error("%s: invalid %s, len %d", __func__,
			    key, ssid_len);
			return __connman_error_invalid_network_name(msg);
		}
		/* TODO(sleffler) update */
	} else if (g_str_equal(key, "WiFi.HiddenSSID") == TRUE ||
		   g_str_equal(key, "HiddenSSID") == TRUE) {
		dbus_bool_t hidden_ssid;

		if (type != DBUS_TYPE_BOOLEAN)
			return __connman_error_invalid_arguments(msg);
		if (service->type != CONNMAN_SERVICE_TYPE_WIFI)
			return __connman_error_invalid_service(msg);

		dbus_message_iter_get_basic(&value, &hidden_ssid);
		if (service->wifi.hidden_ssid != hidden_ssid) {
			service->wifi.hidden_ssid = hidden_ssid;

			property_changed_boolean(service, "Wifi.HiddenSSID",
			    service->wifi.hidden_ssid);

			service_modified(service);
		}
	} else if (g_str_equal(key, "Passphrase") == TRUE) {
		const char *passphrase;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &passphrase);

		if (service->security == CONNMAN_SERVICE_SECURITY_WEP &&
		    __connman_service_parse_wep_key(passphrase,
		        &service->wifi.wep_key_idx,
			&service->wifi.wep_key_len,
			service->wifi.wep_key_matter) == FALSE)
			return __connman_error_invalid_passphrase(msg);


		if ((service->security == CONNMAN_SERVICE_SECURITY_PSK ||
		    service->security == CONNMAN_SERVICE_SECURITY_RSN ||
		    service->security == CONNMAN_SERVICE_SECURITY_WPA) &&
		    __connman_service_validate_passphrase(passphrase) == FALSE)
			return __connman_error_invalid_passphrase(msg);

		g_free(service->passphrase);
		service->passphrase = g_strdup(passphrase);

		/*
		 * NB: Reset last attempt since this new
		 * passphrase has never been tried.
		 */
		service->last_attempt.tv_sec = 0;
		service->last_attempt.tv_usec = 0;

		property_changed_boolean(service, "PassphraseRequired",
		    is_passphrase_required(service));

		if (service->network != NULL)
			prepare_network_passphrase(service);

		service_modified(service);
	} else if (g_str_equal(key, "Profile") == TRUE) {
		const char *path;
		struct connman_profile *profile;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &path);

		profile = __connman_profile_lookup_profile(path);
		/* TODO(sleffler) support setting a NULL profile? */
		if (profile == NULL)
			return __connman_error_not_found(msg);

		if (service->profile != profile) {
			if (service->profile != NULL) {
				/* remove old entry */
				(void) __connman_profile_delete_entry(
				    service->profile, service->identifier);
			}
			service->profile = profile;

			profile_changed(service);

			service_modified(service);
		}
	} else if (g_str_equal(key, kEAPIdentity) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.identity,
                        kEAPIdentity, CONNMAN_WIFI_EAP_IDENTITY);
	} else if (g_str_equal(key, kEAPEAP) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.eap,
                        kEAPEAP, CONNMAN_WIFI_EAP_EAP);
	} else if (g_str_equal(key, kEAPInnerEAP) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.inner_eap,
                        kEAPInnerEAP, CONNMAN_WIFI_EAP_INNEREAP);
	} else if (g_str_equal(key, kEAPAnonymousIdentity) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.anonymous_identity,
                        kEAPAnonymousIdentity,
                        CONNMAN_WIFI_EAP_ANONYMOUSIDENTITY);
	} else if (g_str_equal(key, kEAPClientCert) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.client_cert,
                        kEAPClientCert, CONNMAN_WIFI_EAP_CLIENTCERT);
	} else if (g_str_equal(key, kEAPCertID) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.cert_id,
                        kEAPCertID, CONNMAN_WIFI_EAP_CERTID);
	} else if (g_str_equal(key, kEAPPrivateKey) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.private_key,
                        kEAPPrivateKey, CONNMAN_WIFI_EAP_PRIVATEKEY);
	} else if (g_str_equal(key, kEAPPrivateKeyPassword) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.private_key_passwd,
                        kEAPPrivateKeyPassword,
                        CONNMAN_WIFI_EAP_PRIVATEKEYPASSWORD);
	} else if (g_str_equal(key, kEAPKeyID) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.key_id,
                        kEAPKeyID, CONNMAN_WIFI_EAP_KEYID);
	} else if (g_str_equal(key, kEAPCACert) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.ca_cert,
                        kEAPCACert, CONNMAN_WIFI_EAP_CACERT);
	} else if (g_str_equal(key, kEAPCACertID) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.ca_cert_id,
                        kEAPCACertID, CONNMAN_WIFI_EAP_CACERTID);
	} else if (g_str_equal(key, kEAPCACertNSS) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.ca_cert_nss,
                        kEAPCACertID, CONNMAN_WIFI_EAP_CACERTNSS);
	} else if (g_str_equal(key, kEAPUseSystemCAs) == TRUE) {
		dbus_bool_t use_system_cas;

		if (type != DBUS_TYPE_BOOLEAN)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &use_system_cas);

		if (service->eap.use_system_cas != use_system_cas) {
			service->eap.use_system_cas = use_system_cas;

			connman_network_set_uint8(service->network,
				CONNMAN_WIFI_EAP_USESYSTEMCAS,
				service->eap.use_system_cas);

			property_changed_boolean(service, kEAPUseSystemCAs,
			    service->eap.use_system_cas);

			service_modified(service);
		}
	} else if (g_str_equal(key, kEAPPIN) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.pin,
                        kEAPPIN, CONNMAN_WIFI_EAP_PIN);
	} else if (g_str_equal(key, kEAPPassword) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.password,
                        kEAPPassword, CONNMAN_WIFI_EAP_PASSWORD);
	} else if (g_str_equal(key, kEAPKeyMgmt) == TRUE) {
		reply = set_eap_string(service, msg, type, &value,
                        &service->eap.key_mgmt,
                        kEAPKeyMgmt, CONNMAN_WIFI_EAP_KEY_MGMT);
	} else if (g_str_equal(key, "AutoConnect") == TRUE) {
		dbus_bool_t autoconnect;

		if (type != DBUS_TYPE_BOOLEAN)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &autoconnect);

                /*
                 * This is a request to mark the service for auto connect, but
                 * if favorite isn't set, the setting won't take effect. Thus,
                 * set favorite on the service in the hope that the DBus caller
                 * knows what it's doing.
                 */
		if (service->favorite == FALSE && autoconnect == TRUE)
			connman_service_set_favorite(service, TRUE);

		if (service->autoconnect != autoconnect) {
			service->autoconnect = autoconnect;

			property_changed_boolean(service, "AutoConnect",
			    service->autoconnect);

			service_modified(service);
		}
	} else if (g_str_equal(key, "ProxyConfig") == TRUE) {
		const char *proxy_config;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &proxy_config);

		if (g_strcmp0(proxy_config, service->proxy_config) != 0) {
			g_free(service->proxy_config);
			service->proxy_config = g_strdup(proxy_config);

			property_changed_string(service, "ProxyConfig",
			    service->proxy_config);

			service_recheck_portal_state(service);

			service_modified(service);
		}
	} else if (g_str_equal(key, "Priority") == TRUE) {
		dbus_int32_t pri;

		if (type != DBUS_TYPE_INT32)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &pri);

		if (!(CONNMAN_SERVICE_PRI_MIN <= pri &&
		    pri <= CONNMAN_SERVICE_PRI_MAX)) {
			_DBG_SERVICE("service priority %d out of range", pri);
			return __connman_error_invalid_arguments(msg);
		}

		if (service->pri != pri) {
			service->pri = pri;
			__service_resort(service);

			service_priority_changed(service);

			service_modified(service);
		}
	} else if (g_str_equal(key, "Cellular.APN") == TRUE) {
		reply = set_apn_from_property(service, msg, type, &value);
	} else if (g_str_equal(key, "SaveCredentials") == TRUE) {
		dbus_bool_t save_credentials;

		if (type != DBUS_TYPE_BOOLEAN)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &save_credentials);

		if (service->save_credentials != save_credentials) {
			service->save_credentials = save_credentials;

			/* TODO(sleffler) issue PropertyChanged signal? */

			service_modified(service);
		}
	} else if (g_str_equal(key, "CheckPortal") == TRUE) {
		const char *str;
		enum connman_service_check_portal check_portal;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &str);
		check_portal = check_portal_from_str(str);

		if (service->check_portal != check_portal) {
			service->check_portal = check_portal;

			service_recheck_portal_state(service);

			/* TODO(sleffler) issue PropertyChanged signal? */

			service_modified(service);
		}
	} else if (g_str_equal(key, "GUID") == TRUE) {
		const char *guid;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &guid);

		if (g_strcmp0(guid, service->guid) != 0) {
			GSequenceIter *iter = g_hash_table_lookup(service_hash,
				    service->identifier);

			if (service->guid != NULL) {
				g_hash_table_remove(service_hash,
				    service->guid);
				g_free(service->guid);
			}
			service->guid = g_strdup(guid);
			if (service->guid != NULL)
				g_hash_table_insert(service_hash, service->guid,
				    iter);

			/* TODO(sleffler) issue PropertyChanged signal? */

			service_modified(service);
		}
       } else if (g_str_equal(key, "UIData") == TRUE) {
		const char *uidata;

		if (type != DBUS_TYPE_STRING)
			return __connman_error_invalid_arguments(msg);

		dbus_message_iter_get_basic(&value, &uidata);

		if (g_strcmp0(uidata, service->uidata) != 0) {
			g_free(service->uidata);
			service->uidata = g_strdup(uidata);
			service_modified(service);
		}
	} else if (service->provider != NULL) {
		reply = __connman_provider_set_property(service->provider,
		    msg, key, &value);
	} else {
		_DBG_SERVICE("unknown property, name %s", key);
		return __connman_error_invalid_property(msg);
	}
	return reply;
}

/*
 * Set a collection of service properties from a Dbus
 * message dictionary.  If there are multiple errors
 * we return the first error.
 */
DBusMessage *__connman_service_set_properties(DBusMessage *msg,
    struct connman_service *service)
{
	DBusMessageIter iter, array;
	DBusMessage *reply, *return_reply;

	dbus_message_iter_init(msg, &iter);
	dbus_message_iter_recurse(&iter, &array);

	return_reply = NULL;
	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry;

		dbus_message_iter_recurse(&array, &entry);

		reply = __service_set_property(service, &entry, msg);
		if (reply != NULL) {
			if (return_reply == NULL)
				return_reply = reply;
			else
				dbus_message_unref(reply);
		}

		dbus_message_iter_next(&array);
	}

	return return_reply;
}

/**
 * Create a wifi service with the SSID and security type specified
 * in the DBus message.  Note we do the minimum work here; the caller
 * is assumed to call __connman_service_set_properties or similar to push
 * a complete set of service properties into the created object.
 */
static DBusMessage *__create_wifi_service(DBusMessage *msg,
    struct connman_service **service_out,
    connman_bool_t *created_out)
{
	struct connman_service *service;
	struct connman_network *network;
	struct connman_device *device = NULL;
	DBusMessageIter iter, array;
	const char *mode = "managed", *security = "none";
	const char *ssid = NULL;
	const char *passphrase = NULL;
	unsigned int ssid_len = 0;
	const char *ident;
	char *name, *group;
	connman_bool_t created;

	*service_out = NULL;

	dbus_message_iter_init(msg, &iter);
	dbus_message_iter_recurse(&iter, &array);

	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		switch (dbus_message_iter_get_arg_type(&value)) {
		case DBUS_TYPE_STRING:
			if (g_str_equal(key, "WiFi.Mode") == TRUE ||
					g_str_equal(key, "Mode") == TRUE)
				dbus_message_iter_get_basic(&value, &mode);
			else if (g_str_equal(key, "WiFi.Security") == TRUE ||
					g_str_equal(key, "Security") == TRUE)
				dbus_message_iter_get_basic(&value, &security);
			else if (g_str_equal(key, "WiFi.Passphrase") == TRUE ||
					g_str_equal(key, "Passphrase") == TRUE)
				dbus_message_iter_get_basic(&value, &passphrase);
			else if (g_str_equal(key, "WiFi.SSID") == TRUE ||
					g_str_equal(key, "SSID") == TRUE)
				dbus_message_iter_get_basic(&value, &ssid);
			break;
		}

		dbus_message_iter_next(&array);
	}

	if (g_strcmp0(mode, "managed") != 0) {
		connman_error("%s: invalid mode %s", __func__, mode);
		return __connman_error_invalid_arguments(msg);
	}

	if (ssid == NULL) {
		connman_error("%s: no ssid", __func__);
		return __connman_error_invalid_arguments(msg);
	}

	ssid_len = strlen(ssid);
	if (ssid_len < 1 || ssid_len > MAX_SSID_LENGTH) {
		connman_error("%s: invalid ssid, len %d", __func__, ssid_len);
		return __connman_error_invalid_network_name(msg);
	}

	if (g_strcmp0(security, "none") != 0 &&
	    g_strcmp0(security, "wep") != 0 &&
	    g_strcmp0(security, "802_1x") != 0 &&
	    !ispsk(security)) {
		connman_error("%s: invalid security %s", __func__, security);
		return __connman_error_invalid_arguments(msg);
	}

	if (g_strcmp0(security, "wep") == 0 && passphrase != NULL) {
		connman_uint8_t u8_idx;
		unsigned int wep_key_len;
		connman_uint8_t wep_key_matter[MAX_WEP_KEYSPACE];

		/* validate any wep key */
		if (__connman_service_parse_wep_key(passphrase, &u8_idx,
		    &wep_key_len, wep_key_matter) == FALSE)
			return __connman_error_invalid_passphrase(msg);
	}

	if (ispsk(security) && passphrase != NULL &&
	    __connman_service_validate_passphrase(passphrase) == FALSE)
		return __connman_error_invalid_passphrase(msg);

	device = __connman_element_find_device(CONNMAN_DEVICE_TYPE_WIFI);
	if (device == NULL) {
		connman_error("%s: no wifi device", __func__);
		return __connman_error_invalid_arguments(msg);
	}

	ident = __connman_device_get_ident(device);
	if (ident == NULL) {
		connman_error("%s: no device identifier", __func__);
		return __connman_error_invalid_arguments(msg);
	}

	group = connman_wifi_build_group_name((unsigned char *) ssid, ssid_len,
					      mode, security);
	if (group == NULL) {
		connman_error("%s: cannot build group name", __func__);
		return __connman_error_invalid_arguments(msg);
	}

	name = g_strdup_printf("wifi_%s_%s", ident, group);

	service = __connman_service_lookup(name);
	created = FALSE;
	if (service == NULL) {
		network = create_hidden_wifi(device, ssid, ssid_len, mode,
						security);
		if (network != NULL) {
			/* NB: this creates a service bound to the network */
			connman_network_set_group(network, group);
		}
		service = __connman_service_lookup(name);
		if (service == NULL) {
			connman_error("%s: no hidden service", __func__);

			/* clear network just created */
			connman_network_set_available(network, FALSE);
			/* XXX also potentially clears others */
			__connman_device_cleanup_networks(device);
		}
		service->wifi.hidden_ssid = TRUE;
		g_get_current_time(&service->modified);
		created = TRUE;
	} else if (service->network == NULL) {
		/*
		 * The network is known but the active network for the
		 * associated service was cleared.  This happens when
		 * there was an error on the previous connection request.
		 * Flush any networks so we know there's no duplicate of
		 * the network we're about to create, then create a
		 * network as above and install it as active for the
		 * connect request.  No need to re-lookup the service
		 * as we already have a handle.
		 */
		__connman_device_cleanup_networks(device);
		network = create_hidden_wifi(device, ssid, ssid_len, mode,
						security);
		if (network != NULL)
			connman_network_set_group(network, group);
		service->network = network;
		g_get_current_time(&service->modified);
	}
	g_free(name);
	g_free(group);

	if (service == NULL)
		return __connman_error_invalid_arguments(msg);

	*service_out = service;
	if (created_out != NULL)
		*created_out = created;

	return NULL;
}

/*
 * Compat support to get/create a service object for when no
 * GUID is supplied.  Note we do the minimum work here; the caller
 * is assumed to call __connman_service_set_properties or similar to push
 * a complete set of service properties into the created object.
 */
static DBusMessage *__get_service_compat(DBusMessage *msg,
    const char *type_str,
    struct connman_service **service_out, connman_bool_t *created_out)
{
	switch (__connman_service_string2type(type_str)) {
	case CONNMAN_SERVICE_TYPE_WIFI:
		return __create_wifi_service(msg, service_out, created_out);
	case CONNMAN_SERVICE_TYPE_VPN:
		return __connman_provider_create(msg, service_out, created_out);
	default:
		connman_error("%s: unsupported type %s", __func__, type_str);
		return __connman_error_invalid_arguments(msg);
	}
}

static struct connman_service *__service_lookup_by_guid(const char *guid,
    connman_bool_t *created_out)
{
	struct connman_service *service;
	char *ident;

	/* check for an in-memory service instance */
	service = __connman_service_lookup(guid);
	if (service != NULL) {
		*created_out = FALSE;
		return service;
	}

	/* check the profiles and instantiate a service */
	ident = __connman_profile_find_guid(guid);
	if (ident != NULL) {
		/*
		 * Found the GUID property in the profile; instantiate
		 * an instance (NB: this will be loaded from the same profile).
		 */
		service = __connman_service_get(ident, created_out);
		g_free(ident);
	} else
		*created_out = TRUE;	/* NB: don't care if we return NULL */


	return service;
}

static void __service_release(struct connman_service *service)
{
	if (service->network != NULL) {
		/*
		 * Removing the network from the device will cause
		 * the service to be released because the network
		 * layer calls up into the service layer to clear
		 * the (unreferenced) pointer.
		 */
		connman_device_remove_network(
		    connman_network_get_device(service->network),
		    connman_network_get_identifier(service->network));
	} else
		__connman_service_put(service);
}

/*
 * Helper function for get/create a service object.
 */
static DBusMessage *__get_service(DBusMessage *msg,
    struct connman_service **service_out, connman_bool_t *created_out)
{
	const char *type_str, *guid_str;
	DBusMessageIter iter, array;
	DBusMessage *reply;
	struct connman_service *service = NULL;
	connman_bool_t created = FALSE;

	dbus_message_iter_init(msg, &iter);
	dbus_message_iter_recurse(&iter, &array);

	type_str = guid_str = NULL;
	while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry, value;
		const char *key;

		dbus_message_iter_recurse(&array, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);
		if (dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRING) {
			if (g_str_equal(key, "Type") == TRUE)
				dbus_message_iter_get_basic(&value, &type_str);
			else if (g_str_equal(key, "GUID") == TRUE)
				dbus_message_iter_get_basic(&value, &guid_str);
		}
		dbus_message_iter_next(&array);
	}

	_DBG_SERVICE("type %s guid %s", type_str, guid_str);

	if (guid_str != NULL &&
	    (service = __service_lookup_by_guid(guid_str, &created)) != NULL) {
		/*
		 * Found the service by GUID.
		 */
		if (service != NULL &&
		    service->type == CONNMAN_SERVICE_TYPE_VPN &&
		    service->provider == NULL) {
		    	/*
			 * Instantiate the provider.  The service identifier
			 * is the only thing needed here (e.g. we don't need
			 * the d-bus message to get properties).
			 */
			reply = __connman_provider_create_by_identifier(msg,
			    service->identifier);
			if (reply != NULL)
				return reply;
		}
	} else {
		/*
		 * No GUID was supplied or the GUID was not for a
		 * registered/known object; fall back to using the supplied
		 * properties to find/create a Service object.
		 */
		reply = __get_service_compat(msg, type_str, &service, &created);
		if (reply != NULL)
			return reply;
	}
	if (service == NULL)
		return __connman_error_internal_error(msg);

	_DBG_SERVICE("service %p created %d", service, created);

	/*
	 * Push all/remaining properties.  This may duplicate some
	 * previous work which we deem acceptable (since it allows us
	 * to maintain a single place in the code where we set all
	 * service properties).
	 *
	 * NB: if the service was not created and setting properties
	 * fails then the service may still be modified depending on
	 * the order of properties; i.e. we make no attempt roll-back
	 * service' state so the call is atomic.
	 */
	reply = __connman_service_set_properties(msg, service);
	if (reply != NULL && created == TRUE) {
		__service_release(service);
		service = NULL;
	}
	if (service != NULL)
		service_modified(service);

	if (created_out != NULL)
		*created_out = created;
	if (service_out != NULL)
		*service_out = service;
	return reply;
}

/**
 * __connman_get_service:
 * @msg: DBus message with service properties
 *
 * Get a reference to an existing service or create a new instance.
 * A service is identified by GUID or, if none is specified in the
 * properties, then we fall back to using things like the SSID and
 * security credentials to fabricate a service identifier.
 */
DBusMessage *__connman_get_service(DBusMessage *msg)
{
	DBusMessage *reply;
	struct connman_service *service;
	connman_bool_t created;

	reply = __get_service(msg, &service, &created);
	if (reply != NULL)
		return reply;

	/* TODO(sleffler) complete service instantiation when created */

	if (service->path == NULL) {
		__service_release(service);
		return __connman_error_internal_error(msg);
	}

	return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &service->path,
							DBUS_TYPE_INVALID);
}

/*
 * __connman_configure_service:
 * @msg: DBus message with service properties
 *
 * Set the configuration (properties) of a service and save it to
 * storage.  Any in-memory instance of the service is updated. If
 * no in-memory instance exists one will be (briefly) created and
 * destroyed; this should not be visible to the outside world.
 *
 * A service is identified by GUID or, if none is specified in the
 * properties, then we fall back to using things like the SSID and
 * security credentials to fabricate a service identifier.
 */
DBusMessage *__connman_configure_service(DBusMessage *msg)
{
	DBusMessage *reply;
	struct connman_service *service;
	connman_bool_t created;

	reply = __get_service(msg, &service, &created);
	if (reply != NULL)
		return reply;
	/*
	 * If __get_service created a service object because one
	 * didn't already exist, then we need to get rid of it.
	 */
	if (created == TRUE) {
		/* force a save immediately so service->network is valid */
		__connman_profile_save_service(service);
		__service_release(service);
	}

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

/**
 * __connman_service_get:
 * @identifier: service identifier
 *
 * Get a reference to the identified service.  If one exists
 * just bump the refcount; otherwise create a new service.
 */
static struct connman_service *__connman_service_get(const char *identifier,
    connman_bool_t *created_out)
{
	struct connman_service *service;
	GSequenceIter *iter;

	service = __connman_service_lookup(identifier);
	if (service != NULL) {
		connman_service_ref(service);

		_DBG_SERVICE("service %p refcount %d", service,
		    g_atomic_int_get(&service->refcount));
		if (created_out != NULL)
			*created_out = FALSE;
		return service;
	}

	service = connman_service_create();
	_DBG_SERVICE("service %p", service);
	if (service == NULL)
		return NULL;

	service->identifier = g_strdup(identifier);

	__connman_profile_load_service(service);

	iter = g_sequence_insert_sorted(service_list, service,
						service_compare, NULL);

	g_hash_table_insert(service_hash, service->identifier, iter);
	if (service->guid != NULL)
		g_hash_table_insert(service_hash, service->guid, iter);

	if (created_out != NULL)
		*created_out = TRUE;

	return service;
}

static int service_register(struct connman_service *service)
{
	_DBG_SERVICE("service %p", service);

	if (service->path != NULL)
		return -EALREADY;

	/* NB: we do not prefix this with the profile as that can change */
	service->path = g_strdup_printf("/service/%s", service->identifier);

	g_dbus_register_interface(connection, service->path,
					CONNMAN_SERVICE_INTERFACE,
					service_methods, service_signals,
							NULL, service, NULL);

	__connman_profile_load_service(service);

	__service_resort(service);

	__connman_profile_changed(service->profile, TRUE);

	return 0;
}

/**
 * __connman_service_lookup_from_device:
 * @device: device structure
 *
 * Look up a service by device (reference count will not be increased)
 */
struct connman_service *__connman_service_lookup_from_device(struct connman_device *device)
{
	struct connman_service *service;
	const char *ident;
	char *name;

	ident = __connman_device_get_ident(device);
	if (ident == NULL)
		return NULL;

	name = g_strdup_printf("%s_%s",
				__connman_device_get_type(device), ident);
	service = __connman_service_lookup(name);
	g_free(name);

	return service;
}

/**
 * __connman_service_create_from_device:
 * @device: device structure
 *
 * Look up service by device and if not found, create one.
 */
int __connman_service_create_from_device(struct connman_device *device)
{
	struct connman_service *service;
	const char *ident;
	char *name;

	ident = __connman_device_get_ident(device);
	if (ident == NULL)
		return -EINVAL;

	name = g_strdup_printf("%s_%s", __connman_device_get_type(device),
	    ident);
	service = __connman_service_get(name, NULL);
	g_free(name);
	if (service == NULL)
		return -ENOMEM;

	if (service->path != NULL) {
		/* service already registered; just update state */
		__connman_profile_changed(service->profile, TRUE);
		return 0;
	}

	service->type = __connman_device_get_service_type(device);
	service->autoconnect = FALSE;
	service->device = device;

	/*
	 * Default service to the global profile so it's accessible
	 * to all users of the "device".  This handles setups like
	 * open ethernet but does not work for setups like 802.1x.
	 * However setting up that sort of configuration will
	 * require user/policy intervention in which case the service
	 * can be forced to a different profile (where the certs can
	 * be stored) and this code will honor the profile assignment.
	 */
	__connman_profile_load_device_service(service);

	service_register(service);

	__connman_profile_changed(service->profile, TRUE);

	if (service->favorite == TRUE)
		connman_service_auto_connect(service);

	return 0;
}

void __connman_service_remove_from_device(struct connman_device *device)
{
	struct connman_service *service;
	enum connman_service_type type;

	service = __connman_service_lookup_from_device(device);
	if (service == NULL)
		return;

	type = service->type;

	__connman_service_put(service);
	/* NB: service may be invalid after the call to put */
	service = NULL;

	active_service_changed();

	__connman_notifier_disconnect(type);

	__connman_service_device_auto_connect(device);
}

/**
 * connman_service_lookup_from_network:
 * @network: network structure
 *
 * Look up a service by network (reference count will not be increased)
 */
struct connman_service *connman_service_lookup_from_network(struct connman_network *network)
{
	struct connman_service *service;
	const char *ident, *group;
	char *name;

	ident = __connman_network_get_ident(network);
	if (ident == NULL) {
		connman_error("%s: no ident for network %p", __func__, network);
		return NULL;
	}

	group = connman_network_get_group(network);
	if (group == NULL) {
		DBG(DBG_SERVICE | DBG_NETWORK, "no group for network %p",
		    network);
		return NULL;
	}

	name = g_strdup_printf("%s_%s_%s",
			__connman_network_get_type(network), ident, group);
	service = __connman_service_lookup(name);
	if (service == NULL)
		DBG(DBG_SERVICE | DBG_NETWORK, "no service for %s", name);
	g_free(name);

	return service;
}

const char *__connman_service_get_path(struct connman_service *service)
{
	return service->path;
}

unsigned int __connman_service_get_order(struct connman_service *service)
{
	GSequenceIter *iter;
	unsigned int order = CONNMAN_SERVICE_ORDER_MAX;

	if (service == NULL)
		goto done;

	iter = g_hash_table_lookup(service_hash, service->identifier);
	/* TODO(sleffler) vpn? */
	if (iter != NULL)
		order = g_sequence_iter_get_position(iter);
done:
	return order;
}

/**
 * connman_service_get_time:
 * @service: service structure
 * @state: service state for time
 * @sec: location to return time data (seconds)
 * @usec: location to return time data (microseconds) (may be NULL)
 *
 * Return the time for the last transition to the specified state.
 */
void connman_service_get_time(const struct connman_service *service,
	enum connman_service_state state, long *sec, long *usec)
{
	CONNMAN_ASSERT(sec != NULL);
	*sec = service->last_state[state].tv_sec;
	if (usec != NULL)
		*usec = service->last_state[state].tv_usec;
}

/**
 * connman_service_get_time_delta:
 * @service: service structure
 * @from: service state for start time
 * @to: service state for end time
 * @sec: location to return time data (seconds)
 * @usec: location to return time data (microseconds) (may be NULL)
 *
 * Return the time for the last transition between states.
 * This is useful for calculating things like how long it
 * took to associate, acquire a DHCP lease, etc.
 */
void connman_service_get_time_delta(const struct connman_service *service,
	enum connman_service_state from, enum connman_service_state to,
	long *sec, long *usec)
{
	long secs, usecs;

	CONNMAN_ASSERT(sec != NULL);
	secs = service->last_state[to].tv_sec -
	    service->last_state[from].tv_sec;
	usecs = service->last_state[to].tv_usec -
	    service->last_state[from].tv_usec;
	if (usecs < 0) {
		secs--;
		usecs += 1000 * 1000;
	}

	*sec = secs;
	if (usec != NULL)
		*usec = usecs;
}

/**
 * connman_service_get_error
 * @service: service structure
 *
 * Return the service's error state.
 */
enum connman_service_error connman_service_get_error(const struct
	connman_service *service)
{
	return service->error;
}

/**
 * connman_service_get_frequency
 * @service: service structure
 *
 * Return the service's frequency, if available; otherwise return -1.
 */
int connman_service_get_frequency(const struct connman_service *service)
{
	if (service->network == NULL)
		return -1;
	return (int) connman_network_get_uint16(service->network, "Frequency");
}

/**
 * connman_service_get_phymode
 * @service: service structure
 *
 * Return the service's phy mode, if available; otherwise
 * returns CONNMAN_NETWORK_PHYMODE_UNDEF.
 */
enum connman_network_phymode connman_service_get_phymode(const struct
	connman_service *service)
{
	if (service->network == NULL)
		return CONNMAN_NETWORK_PHYMODE_UNDEF;
	return connman_network_get_phymode(service->network);
}

/**
 * connman_service_get_security
 * @service: service structure
 *
 * Return the service's security characteristics.
 */
enum connman_service_security connman_service_get_security(const struct
	connman_service *service)
{
	return service->security;
}

/**
 * connman_service_get_authmode
 * @service: service structure
 *
 * Return the service's authentication mode, if available; otherwise
 * returns NULL.
 */
const char *connman_service_get_authmode(const struct connman_service *service)
{
	if (service->network == NULL)
		return NULL;
	return connman_network_get_string(service->network,
	    CONNMAN_WIFI_AUTHMODE);
}

/**
 * connman_service_get_type:
 * @service: service structure
 *
 * Return the service type as a string.
 */
const char *connman_service_get_type(const struct connman_service *service)
{
	return service != NULL ?
	    service_type2string(service->type) : "";
}

/**
 * connman_service_check_portal:
 * @service: service structure
 *
 * Return TRUE if the service should have captive portal checks done.
 */
connman_bool_t __connman_service_check_portal(struct connman_service *service)
{
	uint32_t mask;

	switch (service->check_portal) {
	case CONNMAN_SERVICE_CHECK_PORTAL_TRUE:
		return TRUE;
	case CONNMAN_SERVICE_CHECK_PORTAL_FALSE:
		return FALSE;
	default:			/* auto */
		/*
		 * Never check for portal status if the service is configured
		 * to use a proxy because flimflam portal checks do not
		 * respect the proxy settings.  Better to just transition the
		 * service to ONLINE.
		 */
		if (service->proxy_config != NULL)
			return FALSE;

		/* fall back to per-technology control */
		mask = __connman_profile_get_checkportal();
		return (mask & (1<<service->type)) != 0;
	}
}

static gboolean recheck_portaled_services(gpointer arg)
{
	gboolean first_service = TRUE;
	GSequenceIter *iter;

	iter = g_sequence_get_begin_iter(service_list);
	while (g_sequence_iter_is_end(iter) == FALSE) {
		struct connman_service *service = g_sequence_get(iter);
		if (is_portal(service)) {
			if (first_service)
				/* Only recheck the active service */
				service_recheck_portal_state(service);
			/*
			 * reschedule the timeout if any service is in
			 * portal state
			 */
			return TRUE;
		}
		first_service = FALSE;
		iter = g_sequence_iter_next(iter);
	}
	portal_recheck_timeout = 0;
	return FALSE;
}

void __connman_service_recheck_portal(void)
{
	struct connman_service *service = get_active_service();
	if (service != NULL)
		service_recheck_portal_state(service);
}

static enum connman_service_type convert_network_type(struct connman_network *network)
{
	enum connman_network_type type = connman_network_get_type(network);

	switch (type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
		break;
	case CONNMAN_NETWORK_TYPE_WIFI:
		return CONNMAN_SERVICE_TYPE_WIFI;
	case CONNMAN_NETWORK_TYPE_WIMAX:
		return CONNMAN_SERVICE_TYPE_WIMAX;
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
		return CONNMAN_SERVICE_TYPE_BLUETOOTH;
	case CONNMAN_NETWORK_TYPE_CELLULAR:
		return CONNMAN_SERVICE_TYPE_CELLULAR;
	}

	return CONNMAN_SERVICE_TYPE_UNKNOWN;
}

static enum connman_service_mode convert_wifi_mode(const char *mode)
{
	if (mode == NULL)
		return CONNMAN_SERVICE_MODE_UNKNOWN;
	else if (g_str_equal(mode, "managed") == TRUE)
		return CONNMAN_SERVICE_MODE_MANAGED;
	else if (g_str_equal(mode, "adhoc") == TRUE)
		return CONNMAN_SERVICE_MODE_ADHOC;
	else
		return CONNMAN_SERVICE_MODE_UNKNOWN;
}

static enum connman_service_mode convert_wifi_security(const char *security)
{
	if (security == NULL)
		return CONNMAN_SERVICE_SECURITY_UNKNOWN;
	else if (g_str_equal(security, "none") == TRUE)
		return CONNMAN_SERVICE_SECURITY_NONE;
	else if (g_str_equal(security, "wep") == TRUE)
		return CONNMAN_SERVICE_SECURITY_WEP;
	else if (g_str_equal(security, "psk") == TRUE)
		return CONNMAN_SERVICE_SECURITY_PSK;
	else if (g_str_equal(security, "wpa") == TRUE)
		return CONNMAN_SERVICE_SECURITY_WPA;
	else if (g_str_equal(security, "rsn") == TRUE)
		return CONNMAN_SERVICE_SECURITY_RSN;
	else if (g_str_equal(security, "802_1x") == TRUE)
		return CONNMAN_SERVICE_SECURITY_802_1X;
	else
		return CONNMAN_SERVICE_SECURITY_UNKNOWN;
}

/*
 * Update service information with state from a corresponding network.
 * @service: service structure.
 * @network: a network structure associated with the service.
 * @force_current: flag to skip the tests and make the provided
 * network the preferred network.
 *
 * If @force_current is TRUE, then the service's preferred network is
 * updated to the provided network and the signal strength is updated
 * from there. Otherwise, if the service is not connected (and thus
 * the preferred network can float freely), the current preferred
 * network is compared with the provided network, and updated to it if
 * it seems 'better' (stronger signal).
 */
static void update_from_network(struct connman_service *service,
				struct connman_network *network,
				connman_bool_t force_current)
{
	connman_uint8_t strength;
	int scangen;

	/*
	 * A connected or connecting network is automatically the
	 * preferred network; override this only when changing the
	 * connected network.  This is also ensures the service's
	 * signal strength follows the preferred network (consider
	 * a WiFi BSS with multiple BSSID).
	 */
	if (force_current == FALSE &&
	    is_connecting_or_connected(service) == TRUE)
		return;

	if (network == NULL) {
		if (service->network != NULL) {
			_DBG_SERVICE("service %p clear network %p",
			    service, service->network);
			connman_network_unref(service->network);
			service->network = NULL;
		}
		service->strength = 0;
		service->scangen = -1;
		goto done;
	}

	/*
	 * Update signal strength and preferred network.  There are
	 * two cases; if the network's scan generation counter is
	 * different then it must be newer data and we adopt the value.
	 * Otherwise this is another network that maps to the same
	 * service (e.g. by SSID) and we compare signal strength to
	 * choose the preferred network.
	 */
	strength = connman_network_get_strength(network);
	scangen = connman_network_get_scangen(network);
	if (force_current == TRUE ||
	    service->scangen != scangen ||
	    strength > service->strength) {
		service->strength = strength;
		service->scangen = scangen;

		_DBG_SERVICE("service %p network %p -> %p", service,
			     service->network, network);
		if (service->network != network) {
			if (service->network != NULL)
				connman_network_unref(service->network);
			service->network = connman_network_ref(network);
		}

		strength_changed(service);
	} else if (service->network == NULL) {
		/*
		 * Service has no "active network", force it. This
		 * happens when the service is being constructed to
		 * connect to a hidden wifi network, or when the first
		 * network associated with a non-connected service
		 * goes away; setting the active network is necessary
		 * for the connection logic to proceed.
		 */
		_DBG_SERVICE("service %p bind network %p", service, network);
		service->network = connman_network_ref(network);
	}
done:
	__service_resort(service);
}

/**
 * __connman_service_create_from_network:
 * @network: network structure
 *
 * Associate a network with a service, potentially creating a new
 * service object.  We bump the refcount on the service for each
 * associated network but only hold a reference to the ``current
 * network'' object (see connman_service_set_current_network).
  Callers are required to call __connman_service_remove_from_network
 * for each network to drop the service refcount.
 */
int __connman_service_create_from_network(struct connman_network *network)
{
	struct connman_service *service;
	const char *str, *ident, *group;
	char *name;

	ident = __connman_network_get_ident(network);
	if (ident == NULL)
		return -EINVAL;

	group = connman_network_get_group(network);
	if (group == NULL)
		return -EINVAL;

	name = g_strdup_printf("%s_%s_%s", __connman_network_get_type(network),
	    ident, group);
	service = __connman_service_get(name, NULL);
	g_free(name);
	if (service == NULL)
		return -ENOMEM;

	/* TODO(sleffler) this is utter crap; can we kill it? */
	if (__connman_network_get_weakness(network) == TRUE) {
		connman_info("%s: ignore network with strength %d", __func__,
		    connman_network_get_strength(network));
		return 0;
	}

	if (service->path != NULL) {
		/* service already registered; just update state */
		update_from_network(service, network, FALSE);
		__connman_profile_changed(service->profile, TRUE);
		return 0;
	}

	service->type = convert_network_type(network);
	service->autoconnect = (service->type == CONNMAN_SERVICE_TYPE_WIFI ||
				service->type == CONNMAN_SERVICE_TYPE_CELLULAR);

	/* TODO(sleffler) notify FALSE? */
	state_change(service, CONNMAN_SERVICE_STATE_IDLE, FALSE);

	str = connman_network_get_string(network, "Name");
	if (str != NULL) {
		service->name = g_strdup(str);
		/*
		 * NB: if network was previously known hidden then this
		 * will be overridden below when we retrieve properties
		 * for the service from the profile.
		 */
		service->hidden = FALSE;
	} else {
		service->hidden = TRUE;
	}

	str = connman_network_get_string(network, "WiFi.Mode");
	service->mode = convert_wifi_mode(str);

	str = connman_network_get_string(network, "WiFi.Security");
	service->security = convert_wifi_security(str);

	update_from_network(service, network, FALSE);

	/* TODO(sleffler) __connman_service_get already does this, needed? */
	__connman_profile_load_service(service);

	service_register(service);

	__connman_profile_changed(service->profile, TRUE);

	if (service->favorite == TRUE)
		connman_service_auto_connect(service);

	return 0;
}

/**
 * connman_service_set_current_network:
 * @network: network structure
 *
 * Set the ``current network'' of any service associated with the
 * specified network.  This is used when roaming between networks
 * for services that support multiple networks (e.g. WiFi).
 */
void connman_service_set_current_network(struct connman_network *network)
{
	struct connman_service *service;

	service = connman_service_lookup_from_network(network);
	if (service == NULL) {
		connman_error("%s: no service for network %p",
		    __func__, network);
		return;
	}

	update_from_network(service, network, TRUE);

	property_changed_string(service, CONNMAN_WIFI_BSSID,
	    connman_network_get_string(network, "Address"));
}

/**
 * __connman_service_update_strength:
 * @network: network structure
 *
 * Update service signal strength after a network's signal strength
 * has been updated.  If the network is the preferred/active network
 * then we update directly.  Otherwise follow the normal update path
 * whereby signal strength is propagated based on the state of the
 * service and the network (i.e scangen+strength).
 */
void __connman_service_update_strength(struct connman_network *network)
{
	struct connman_service *service;

	service = connman_service_lookup_from_network(network);
	if (service == NULL) {
		_DBG_SERVICE("no service for network %p", network);
		return;
	}
	if (service->network == network) {
		connman_uint8_t strength;

		service->scangen = connman_network_get_scangen(network);
		strength = connman_network_get_strength(network);
		_DBG_SERVICE("scangen %d strength %d -> %d", service->scangen,
		    service->strength, strength);
		if (strength != service->strength) {
			service->strength = strength;

			strength_changed(service);
			__service_resort(service);
		}
	} else
		update_from_network(service, network, FALSE);
}

/**
 * __connman_service_update_network:
 * @network: network structure
 *
 * Like __connman_service_update_strength but only update signal
 * strength after checking the network state.  This is mainly used
 * to update network/service binding during connection requests.
 *
 * TODO(sleffler) too subtle/confusing; probably can merge w/ above.
 */
void __connman_service_update_from_network(struct connman_network *network)
{
	struct connman_service *service;

	service = connman_service_lookup_from_network(network);
	if (service == NULL) {
		connman_error("%s: no service for network %p",
		    __func__, network);
		return;
	}

	update_from_network(service, network, FALSE);
}

/*
 * __connman_service_remove_from_network:
 * @network: network structure
 *
 * Remove the association between a network and a service.  If a service
 * exists the refcount is dropped and the object potentially reclaimed.
 * If the network is the ``active network'' for the service then it is
 * disconnected and the held reference is released.
 */
void __connman_service_remove_from_network(struct connman_network *network)
{
	struct connman_service *service;

	service = connman_service_lookup_from_network(network);
	if (service == NULL)
		return;

	_DBG_SERVICE("service %p network %p", service, network);

	if (service->network == network) {
		/*
		 * If the network is being removed while the service is in a
		 * active or connecting state, we need to clean that up first.
		 */
		if (is_connected(service) ||
		    is_connecting(service)) {
			_DBG_SERVICE("network %p removed from active "
				     "service %p", network, service);
			__connman_service_disconnect(service);
			active_service_changed();
			__connman_notifier_disconnect(service->type);
		}

		/*
		 * Recalculate the preferred network (if any).  In
		 * particular this ensures the device associated with
		 * the service can be found (via the network reference).
		 */
		update_from_network(service,
		    __connman_network_find_other_network(network), TRUE);
	}

	__connman_service_put(service);
	/* NB: service may be invalid after the call to put */
}

/**
 * __connman_service_create_from_provider:
 * @provider: provider structure
 *
 * Look up service by provider and if not found, create one.
 * The caller is responsible for releasing the reference to
 * returned object.
 */
struct connman_service *
__connman_service_create_from_provider(struct connman_provider *provider)
{
	struct connman_service *service;
	const char *ident, *str;

	_DBG_SERVICE("provider %p", provider);

	ident = connman_provider_get_ident(provider);
	if (ident == NULL)
		return NULL;

	service = __connman_service_lookup(ident);
	if (service == NULL) {
		service = __connman_service_get(ident, NULL);
		if (service == NULL)
			return NULL;
	}

	_DBG_SERVICE("service %p ident %s", service, ident);

	service->type = CONNMAN_SERVICE_TYPE_VPN;
	/*
	 * NB: No reference is held on the provider; we assume the
	 * service and provider have equal lifetime and providers
	 * are reclaimed in service_free (when the service goes away).
	 */
	service->provider = provider;
	service->autoconnect = FALSE;

	service->state = CONNMAN_SERVICE_STATE_IDLE;

	str = connman_provider_get_string(provider, CONNMAN_PROVIDER_NAME);
	if (str != NULL) {
		g_free(service->name);
		service->name = g_strdup(str);
		service->hidden = FALSE;
	} else {
		g_free(service->name);
		service->name = NULL;
		service->hidden = TRUE;
	}

	service->strength = 0;

	service_register(service);

	return service;
}

struct connman_network_apn *
__connman_service_get_apn(struct connman_service *service)
{
	if (service->cellular.apn.apn != NULL)
		return &service->cellular.apn;
	else
		return NULL;
}

const struct connman_network_apn *
connman_service_get_last_good_apn(const struct connman_service *service)
{
	if (service->cellular.last_good_apn.apn != NULL)
		return &service->cellular.last_good_apn;
	else
		return NULL;
}

void __connman_service_save_last_good_apn(struct connman_service *service,
					  const struct connman_network_apn *apn)
{
	if (apn == NULL && service->cellular.last_good_apn.apn == NULL)
		return;

	g_free(service->cellular.last_good_apn.apn);
	g_free(service->cellular.last_good_apn.network_id);
	g_free(service->cellular.last_good_apn.username);
	g_free(service->cellular.last_good_apn.password);

	if (apn != NULL) {
		service->cellular.last_good_apn.apn = g_strdup(apn->apn);
		service->cellular.last_good_apn.network_id = g_strdup(apn->network_id);
		service->cellular.last_good_apn.username = g_strdup(apn->username);
		service->cellular.last_good_apn.password = g_strdup(apn->password);
	} else {
		service->cellular.last_good_apn.apn = NULL;
		service->cellular.last_good_apn.network_id = NULL;
		service->cellular.last_good_apn.username = NULL;
		service->cellular.last_good_apn.password = NULL;
	}
	__connman_profile_save_service(service);
	connman_dbus_send_property_changed_string_dict(service->path,
					  CONNMAN_SERVICE_INTERFACE,
					  "Cellular.LastGoodAPN", append_apn,
					  &service->cellular.last_good_apn);
}

enum connman_network_cellular_roaming_state
	__connman_service_get_roaming_state(struct connman_service *service)
{
	return service->cellular.roaming_state;
}

void __connman_service_disconnect_if_roaming(struct connman_service *service) {
	if (service->cellular.roaming_state == CONNMAN_NETWORK_ROAMING_STATE_ROAMING &&
	    service->state == CONNMAN_SERVICE_STATE_READY)
		__connman_service_disconnect(service);
}

const char *
connman_service_get_usage_url(const struct connman_service *service)
{
	CONNMAN_ASSERT(service != NULL);
	return service->cellular.usage_url;
}

void
connman_service_create_sticky_route(struct connman_service *service,
				    const char *ip_addr)
{
	CONNMAN_ASSERT(service != NULL);
	if (service->sticky_route != NULL)
		del_sticky_route(service->sticky_route);
	service->sticky_route = new_sticky_route(service, ip_addr);
	if (service->sticky_route == NULL)
		connman_error("%s: create sticky route failed", __func__);
        property_changed_string(service, "StickyHostRoute",
	    service->sticky_route ? service->sticky_route->dest : "");
}

const char *
connman_service_get_sticky_route(const struct connman_service *service)
{
	CONNMAN_ASSERT(service != NULL);
	if (service->sticky_route == NULL)
		return NULL;
	return service->sticky_route->dest;
}

static void service_load_string(GKeyFile *keyfile,
    struct connman_service *service, const char *key, char **storage)
{
	char *str = g_key_file_get_string(keyfile, service->identifier,
	    key, NULL);
	g_free(*storage);
	*storage = str;
}

static void service_load_encrypted(GKeyFile *keyfile,
    struct connman_service *service, const char *key, char **storage)
{
	char *str = __connman_storage_get_encrypted_value(keyfile,
	    service->identifier, key);
	g_free(*storage);
	*storage = str;
}

static void service_load_iso8601(GKeyFile *keyfile,
    struct connman_service *service, const char *key, GTimeVal *storage)
{
	char *str = g_key_file_get_string(keyfile, service->identifier,
	    key, NULL);
	if (str != NULL) {
		g_time_val_from_iso8601(str, storage);
		g_free(str);
	} else {
		storage->tv_sec = 0;
		storage->tv_usec = 0;
	}
}

static void service_load_boolean(GKeyFile *keyfile,
    struct connman_service *service, const char *key,
    connman_bool_t def_value, connman_bool_t *storage)
{
	GError *error = NULL;
	gboolean value = g_key_file_get_boolean(keyfile, service->identifier,
	    key, &error);
	*storage = (error == NULL) ? value : def_value;
}

static void service_load_apn_string(GKeyFile *keyfile,
    struct connman_service *service, const char *keytag, const char *apntag,
    char **storage)
{
	/* TODO(sleffler) check return value or use stack variable */
	gchar *key = g_strdup_printf("Cellular.%s.%s", keytag, apntag);
	service_load_string(keyfile, service, key, storage);
	g_free(key);
}

static void service_load_apn(GKeyFile *keyfile, struct connman_service *service,
    const char *key, struct connman_network_apn *apn)
{
	service_load_apn_string(keyfile, service, key, "apn", &apn->apn);
	service_load_apn_string(keyfile, service, key, "username",
	    &apn->username);
	service_load_apn_string(keyfile, service, key, "password",
	    &apn->password);
	service_load_apn_string(keyfile, service, key, "network_id",
	    &apn->network_id);
}

/*
 * Construct old-style entries for wpa/rsn services.
 */
static char *__old_security(GKeyFile *keyfile,
    const struct connman_service *service)
{
	char *group = NULL;

	if (service->security == CONNMAN_SERVICE_SECURITY_WPA) {
		group = g_strdup(service->identifier);
		g_strlcpy(group + strlen(group) - 3, "wpa", 4);
		if (g_key_file_has_group(keyfile, group) == FALSE) {
			g_free(group);
			group = NULL;
		}
	} else if (service->security == CONNMAN_SERVICE_SECURITY_RSN) {
		group = g_strdup(service->identifier);
		g_strlcpy(group + strlen(group) - 3, "rsn", 4);
		if (g_key_file_has_group(keyfile, group) == FALSE) {
			g_free(group);
			group = NULL;
		}
	}
	return group;
}

static int service_load(struct connman_service *service, GKeyFile *keyfile)
{
	const char *group = service->identifier;
	GError *error = NULL;
	gchar *str;
	unsigned int ssid_len;
	int err = 0, pri;

	_DBG_SERVICE("service %p", service);

	if (g_key_file_has_group(keyfile, group) == FALSE) {
		char *old_group;
		/*
		 * Handle upgrade of old-style entries for wpa/rsn services.
		 */
		if (g_str_has_suffix(group, "_psk") == FALSE) {
			err = -ESRCH;
			goto done;
		}
		old_group = __old_security(keyfile, service);
		if (old_group == NULL) {
			err = -ESRCH;
			goto done;
		}
		_DBG_SERVICE("check old entry %s", old_group);
		group = old_group;
	}

	/* NB: for loading a service object by GUID */
	str = g_key_file_get_string(keyfile, group, "Type", NULL);
	if (str != NULL) {
		service->type = __connman_service_string2type(str);
		g_free(str);
	}

	switch (service->type) {
	case CONNMAN_SERVICE_TYPE_UNKNOWN:
	case CONNMAN_SERVICE_TYPE_ETHERNET:
	case CONNMAN_SERVICE_TYPE_VPN:
		break;
	case CONNMAN_SERVICE_TYPE_WIFI:
		str = g_key_file_get_string(keyfile, group, "WiFi.Security",
		    NULL);
		if (str != NULL) {
			service->security = convert_wifi_security(str);
			g_free(str);
		}
		str = g_key_file_get_string(keyfile, group, "WiFi.Mode", NULL);
		if (str != NULL) {
			service->mode = convert_wifi_mode(str);
			g_free(str);
		}

		service_load_boolean(keyfile, service, "WiFi.HiddenSSID",
		    FALSE, &service->wifi.hidden_ssid);

		/* TODO(sleffler) optimization may break profile push */
		if (service->name == NULL) {
			service->name = g_key_file_get_string(keyfile,
					group, "Name", NULL);

			if (service->network != NULL && service->name != NULL)
				connman_network_set_name(service->network,
						service->name);
		}

		if (service->network != NULL &&
				connman_network_get_blob(service->network,
					"WiFi.SSID", &ssid_len) == NULL) {
			gchar *hex_ssid;

			hex_ssid = g_key_file_get_string(keyfile,
							group, "SSID", NULL);

			if (hex_ssid != NULL) {
				struct blob *ssid;

				err = blob_new_from_hex(&ssid, hex_ssid);
				g_free(hex_ssid);
				if (err == 0) {
					connman_network_set_blob(
					    service->network, "WiFi.SSID",
					    ssid->data, ssid->len);
					blob_free(ssid);
				} else {
					/*
					 * NB: don't goto done; callers are
					 * not prepared for partial success
					 */
					connman_error("%s: bad ssid blob for "
					    "%s ignored (error %d)", __func__,
					    group, err);
				}
			}
		}
		/* fall through */

	case CONNMAN_SERVICE_TYPE_WIMAX:
	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
		str = g_key_file_get_string(keyfile, group, "Failure", NULL);
		if (str != NULL) {
			service->error = string2error(str);
			g_free(str);
			state_change(service, CONNMAN_SERVICE_STATE_FAILURE,
			    FALSE);
		} else
			service->error = CONNMAN_SERVICE_ERROR_NO_ERROR;
		/* fall through */

	case CONNMAN_SERVICE_TYPE_CELLULAR:
		service_load_boolean(keyfile, service, "Favorite",
		    FALSE, &service->favorite);
		service_load_boolean(keyfile, service, "PreviouslyConnected",
		    FALSE, &service->previously_connected);
		service_load_boolean(keyfile, service, "AutoConnect",
		    FALSE, &service->autoconnect);
		break;
	default:
		break;
	}

	service_load_iso8601(keyfile, service, "Modified",
	    &service->modified);
	service_load_iso8601(keyfile, service, "LastAttempt",
	    &service->last_attempt);

	service_load_boolean(keyfile, service, "SaveCredentials",
	    TRUE, &service->save_credentials);

	str = g_key_file_get_string(keyfile, group, "CheckPortal", NULL);
	if (str != NULL) {
		service->check_portal = check_portal_from_str(str);
		g_free(str);
	} else
		service->check_portal = CONNMAN_SERVICE_CHECK_PORTAL_AUTO;

	/* TODO(sleffler) optimize? */
	service_clear_passphrase(service);
	str = __connman_storage_get_encrypted_value(keyfile, group,
	    "Passphrase");
	if (str != NULL) {
		connman_bool_t valid_pass = TRUE;
		switch (service->security) {
		case CONNMAN_SERVICE_SECURITY_WEP:
			valid_pass = __connman_service_parse_wep_key(str,
			    &service->wifi.wep_key_idx,
			    &service->wifi.wep_key_len,
			    service->wifi.wep_key_matter);
			break;
		case CONNMAN_SERVICE_SECURITY_WPA:
		case CONNMAN_SERVICE_SECURITY_RSN:
		case CONNMAN_SERVICE_SECURITY_PSK:
			valid_pass = __connman_service_validate_passphrase(str);
			break;
		default:
			break;
		}
		if (valid_pass == TRUE)
			service->passphrase = str;
		else
			g_free(str);
	}

	service_load_encrypted(keyfile, service, kEAPIdentity,
	    &service->eap.identity);
	service_load_string(keyfile, service, kEAPEAP,
	    &service->eap.eap);
	service_load_string(keyfile, service, kEAPInnerEAP,
	    &service->eap.inner_eap);
	service_load_encrypted(keyfile, service, kEAPAnonymousIdentity,
	    &service->eap.anonymous_identity);
	service_load_string(keyfile, service, kEAPClientCert,
	    &service->eap.client_cert);
	service_load_string(keyfile, service, kEAPCertID,
	    &service->eap.cert_id);
	service_load_string(keyfile, service, kEAPPrivateKey,
	    &service->eap.private_key);
	service_load_encrypted(keyfile, service, kEAPPrivateKeyPassword,
	    &service->eap.private_key_passwd);
	service_load_string(keyfile, service, kEAPKeyID,
	    &service->eap.key_id);
	service_load_string(keyfile, service, kEAPCACert,
	    &service->eap.ca_cert);
	service_load_string(keyfile, service, kEAPCACertID,
	    &service->eap.ca_cert_id);
	service_load_string(keyfile, service, kEAPCACertNSS,
	    &service->eap.ca_cert_nss);
	service_load_boolean(keyfile, service, kEAPUseSystemCAs,
	    TRUE, &service->eap.use_system_cas);
	service_load_string(keyfile, service, kEAPPIN,
	    &service->eap.pin);
	service_load_encrypted(keyfile, service, kEAPPassword,
	    &service->eap.password);
	service_load_string(keyfile, service, kEAPKeyMgmt,
	    &service->eap.key_mgmt);

	service_load_string(keyfile, service, "ProxyConfig",
	    &service->proxy_config);
	service_load_string(keyfile, service, "GUID",
	    &service->guid);
	service_load_string(keyfile, service, "UIData",
	    &service->uidata);

	pri = g_key_file_get_integer(keyfile, group, "Priority", &error);
	service->pri = (error == NULL) ? pri : CONNMAN_SERVICE_PRI_NONE;

	service_load_apn(keyfile, service, "APN", &service->cellular.apn);
	service_load_apn(keyfile, service, "LastGoodAPN",
	    &service->cellular.last_good_apn);
done:
	if (group != service->identifier)
		g_free((char *)group);

	return err;
}

static void service_save_integer(GKeyFile *keyfile,
    struct connman_service *service, const char *key, gboolean isvalid,
    int value)
{
	if (isvalid)
		g_key_file_set_integer(keyfile, service->identifier,
					key, value);
	else
		g_key_file_remove_key(keyfile, service->identifier,
					key, NULL);
}

static void service_save_boolean(GKeyFile *keyfile,
    struct connman_service *service, const char *key, gboolean isvalid,
    gboolean value)
{
	if (isvalid)
		g_key_file_set_boolean(keyfile, service->identifier,
						key, value);
	else
		g_key_file_remove_key(keyfile, service->identifier,
						key, NULL);
}

static void service_save_iso8601(GKeyFile *keyfile,
    struct connman_service *service, const char *key, gboolean isvalid,
    GTimeVal *timeval)
{
	gchar *str = NULL;
	if (isvalid && (str = g_time_val_to_iso8601(timeval)) != NULL)
		g_key_file_set_string(keyfile, service->identifier, key, str);
	else
		g_key_file_remove_key(keyfile, service->identifier, key, NULL);
	g_free(str);
}

static void service_save_string(GKeyFile *keyfile,
    struct connman_service *service, const char *key, const char *value)
{
	if (isvalidstr(value))
		g_key_file_set_string(keyfile, service->identifier, key, value);
	else
		g_key_file_remove_key(keyfile, service->identifier, key, NULL);
}

static void service_save_encrypted(GKeyFile *keyfile,
    struct connman_service *service, const char *key, const char *value)
{
	if (isvalidstr(value)) {
		__connman_storage_set_encrypted_value(keyfile, 
		    service->identifier, key, value);
	} else
		g_key_file_remove_key(keyfile, service->identifier, key, NULL);
}

static void service_save_eap(GKeyFile *keyfile, struct connman_service *service)
{
	gboolean save_creds = service->save_credentials;

	service_save_encrypted(keyfile, service, kEAPIdentity,
	    save_creds ? service->eap.identity : NULL);
	service_save_string(keyfile, service, kEAPEAP, service->eap.eap);
	service_save_string(keyfile, service, kEAPInnerEAP,
	    service->eap.inner_eap);
	service_save_encrypted(keyfile, service, kEAPAnonymousIdentity,
	    save_creds ? service->eap.anonymous_identity : NULL);
	service_save_string(keyfile, service, kEAPClientCert,
	    save_creds ? service->eap.client_cert : NULL);
	service_save_string(keyfile, service, kEAPCertID,
	    save_creds ? service->eap.cert_id: NULL);
	service_save_string(keyfile, service, kEAPPrivateKey,
	    save_creds ? service->eap.private_key : NULL);
	service_save_encrypted(keyfile, service,
	    kEAPPrivateKeyPassword,
	    save_creds ? service->eap.private_key_passwd : NULL);
	service_save_string(keyfile, service, kEAPKeyID,
	    save_creds ? service->eap.key_id : NULL);
	/* NB: CA cert info is public so we always save it */
	service_save_string(keyfile, service, kEAPCACert,
	    service->eap.ca_cert);
	service_save_string(keyfile, service, kEAPCACertID,
	    service->eap.ca_cert_id);
	/* NB: only save false value to reduce data bloat */
	service_save_boolean(keyfile, service, kEAPUseSystemCAs,
	    service->eap.use_system_cas == FALSE, FALSE);
	service_save_string(keyfile, service, kEAPPIN,
	    save_creds ? service->eap.pin : NULL);
	service_save_encrypted(keyfile, service, kEAPPassword,
	    save_creds ? service->eap.password : NULL);
	service_save_string(keyfile, service, kEAPKeyMgmt,
	    service->eap.key_mgmt);
}

static void service_save_apn_string(GKeyFile *keyfile,
    struct connman_service *service, const char *keytag, const char *apntag,
    const char *value)
{
	/* TODO(sleffler) check return value or use stack variable */
	gchar *key = g_strdup_printf("Cellular.%s.%s", keytag, apntag);
	if (isvalidstr(value))
		g_key_file_set_string(keyfile, service->identifier, key, value);
	else
		g_key_file_remove_key(keyfile, service->identifier, key, NULL);
	g_free(key);
}

static void service_save_apn(GKeyFile *keyfile, struct connman_service *service,
    const char *key, const struct connman_network_apn *apn)
{
	service_save_apn_string(keyfile, service, key, "apn", apn->apn);
	service_save_apn_string(keyfile, service, key, "username",
	    apn->username);
	service_save_apn_string(keyfile, service, key, "password",
	    apn->password);
	service_save_apn_string(keyfile, service, key, "network_id",
	    apn->network_id);
}

static int service_save(struct connman_service *service, GKeyFile *keyfile)
{
	_DBG_SERVICE("service %p%s", service,
	    service->save_to_profile == FALSE ? " [NO SAVE]" : "");

	if (service->save_to_profile == FALSE)
		goto done;

	service_save_string(keyfile, service, "Type",
	    service_type2string(service->type));
	service_save_string(keyfile, service, "Name", service->name);
	service_save_string(keyfile, service, "GUID", service->guid);
	service_save_string(keyfile, service, "UIData", service->uidata);
	service_save_string(keyfile, service, "ProxyConfig",
	    service->proxy_config);

	service_save_integer(keyfile, service, "Priority",
	    service->pri != CONNMAN_SERVICE_PRI_NONE, service->pri);

	service_save_string(keyfile, service, "CheckPortal",
	    service->check_portal != CONNMAN_SERVICE_CHECK_PORTAL_AUTO ?
		check_portal_to_str(service->check_portal) : NULL);

	service_save_iso8601(keyfile, service, "Modified",
	    TRUE, &service->modified);
	service_save_iso8601(keyfile, service, "LastAttempt",
	    is_timeset(&service->last_attempt), &service->last_attempt);

	switch (service->type) {
	case CONNMAN_SERVICE_TYPE_UNKNOWN:
	case CONNMAN_SERVICE_TYPE_ETHERNET:
	case CONNMAN_SERVICE_TYPE_VPN:
		break;
	case CONNMAN_SERVICE_TYPE_WIFI:
		service_save_string(keyfile, service, "WiFi.Security",
		    security2string(service->security));
		service_save_string(keyfile, service, "WiFi.Mode",
		    mode2string(service->mode));
		service_save_boolean(keyfile, service,
		    "WiFi.HiddenSSID", TRUE, service->wifi.hidden_ssid);
		if (service->network) {
			char *ssid =
			    connman_network_get_hex_ssid(service->network);
			service_save_string(keyfile, service, "SSID", ssid);
			g_free(ssid);
		}
		/* fall through */
	case CONNMAN_SERVICE_TYPE_WIMAX:
	case CONNMAN_SERVICE_TYPE_BLUETOOTH:
		service_save_string(keyfile, service, "Failure",
		    service->state == CONNMAN_SERVICE_STATE_FAILURE ?
		        error2string(service->error) : NULL);
		/* fall through */
	case CONNMAN_SERVICE_TYPE_CELLULAR:
		service_save_boolean(keyfile, service, "Favorite",
		    TRUE, service->favorite);
		service_save_boolean(keyfile, service, "AutoConnect",
		    service->favorite, service->autoconnect);
		service_save_boolean(keyfile, service, "PreviouslyConnected",
		    TRUE, service->previously_connected);
		break;
	default:
		break;
	}

	service_save_encrypted(keyfile, service, "Passphrase",
	    service->save_credentials == TRUE ? service->passphrase : NULL);
	/* NB: only save false value to reduce data bloat */
	service_save_boolean(keyfile, service, "SaveCredentials",
	    service->save_credentials == FALSE, FALSE);

	service_save_eap(keyfile, service);

	service_save_apn(keyfile, service, "APN", &service->cellular.apn);
	service_save_apn(keyfile, service, "LastGoodAPN",
	    &service->cellular.last_good_apn);
done:
	return 0;
}

static struct connman_storage service_storage = {
	.name		= "service",
	.priority	= CONNMAN_STORAGE_PRIORITY_LOW,
	.service_load	= service_load,
	.service_save	= service_save,
};

static struct connman_notifier service_notifier = {
	.name		= "service",
	.priority	= CONNMAN_NOTIFIER_PRIORITY_DEFAULT,
	.service_state_changed = notify_state_changed,
	.system_suspend = service_system_suspend,
	.system_resume  = service_system_resume,
	.system_shutdown = service_system_shutdown,
	.profile_push	= service_profile_push,
	.profile_pop	= service_profile_pop,
};

int __connman_service_init(const char *option_order)
{
	_DBG_SERVICE("");

	connection = connman_dbus_get_connection();

	if (connman_storage_register(&service_storage) < 0)
		connman_error("Failed to register service storage");
	if (connman_notifier_register(&service_notifier) < 0)
		connman_error("Failed to register service notifier");

	service_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
								NULL, NULL);

	service_list = g_sequence_new(service_free);

	/* set default service priority ordering */
	__connman_service_set_ordering(option_order != NULL ?
	     option_order : CONNMAN_SERVICE_PRIO_DEFAULT);

	auto_connect_queue = g_queue_new();
	memset(&svc_watchlist, 0, sizeof(svc_watchlist));

	return 0;
}

void __connman_service_cleanup(void)
{
	GSequence *services;

	_DBG_SERVICE("");

	if (portal_recheck_timeout != 0) {
		g_source_remove(portal_recheck_timeout);
		portal_recheck_timeout = 0;
	}

	g_queue_free(auto_connect_queue);

	/* prevent access to the service list while freeing it */
	services = service_list;
	service_list = NULL;
	g_sequence_free(services);

	g_hash_table_destroy(service_hash);
	service_hash = NULL;

	connman_storage_unregister(&service_storage);
	connman_notifier_unregister(&service_notifier);

	dbus_connection_unref(connection);
}
